Guide to ES6
by Vijay Menon
Head Organizer, JavaScriptLA, HackBuddy.com
JOIN US ON DISCORD OR SLACK!
FOLLOW US ON YOUTUBE FOR NEW VIDEOS
WE ALSO BLOG:
Agenda
- The Basics / Tooling
- Variables
- Functions, Arrow Functions
- Spread / Rest Operators
- Destructuring, Classes
- Promises/Generators / Iterators
- Fetch / Async / Await
- Sets / Maps
- JavaScript Modules
- Decorators
- Using TypeScript
Getting Started / Tooling
Lots of different ways to get started, but we'll use Babel & Webpack as our starter kit.
https://github.com/vijayxtreme/starterES6Webpack
Alternatively, you can use another toolkit like Gulp/Traceur, Rollup, or even just try out ES6 in your browser if it supports the syntax (e.g. Google Chrome)
Unfortunately, tooling can sometimes be the biggest handicap to learning new JavaScript
Variables
let
const
var
Use let when you want to have block scope; use const when you want to make sure your variable value can't be overridden.
When would using var still be appropriate?
CODE SAMPLES
let x = "Hello"
if(x == "Hello") {
let y = " World"
}
console.log(x + y)
let x = "Hello"
if(x == "Hello") {
let y = " World"
console.log(x + y)
}
What do you expect as the output here?
What do you expect as the output here?
CODE SAMPLES
const $ = function jQuery(){ //do jquery stuff }
/*** adding in other libraries into our JavaScript ***/
$ = function angular(){ //do angular stuff }
console.log($)
What do you expect as the output here?
const API_KEY = '123456'
API_KEY = '654321'; //someone tries to change this variable's value
What do you expect as the output here?
TEMPLATE STRINGS
Template strings are great for concatenating lots of variables with text. In the old days of ES5, we had to do stuff like this:
Now we can use the backtick symbol ` to start and end a template string, and use ${} syntax to plug in variables like so:
var x = 10, y=20, a=40, b=15;
var result = "Robert has " + x + " apples, " + y + " bananas, " + a + " mangoes, " + b + " ice creams." ;
let x = 10, y = 20, a = 40, b = 15
let result = `Robert has ${x} apples, ${y}
bananas, ${a} mangoes, ${b} ice creams`
Result is cleaner, easier to read, write syntax; and template strings don't care about multiple lines!
TEMPLATE STRINGS AS FUNCTIONS
let parseTemplate = function(text, variable1, variable2){
console.log(text);
console.log(variable1);
console.log(variable2);
return variable2;
}
let item1 = "Laundry"
let item2 = function(){ return "Groceries" }
let result = parseTemplate`I have to do my ${item1} and ${item2}`
console.log(`Finished with ${result()}`)
GRAPHQL USING TEMPLATE STRINGS
export const query = graphql`
query {
allWordpressPost {
nodes {
title
excerpt
link
featured_media {
source_url
}
}
}
}`
Don't freak out if you don't understand this yet, just try to get the general idea :)
ARROW Functions
Function expressions that define functions can now be replaced with "arrow function" syntax. Here's single parameter and multiple parameters below.
Note: If you have no params, write your function like so: () => { /*My code here */}
let greetName = name => `hello ${name}`
// ES5 syntax
var greetName = function(name) {
return "hello " + name;
}
console.log(greetName('vijay'))
//hello vijay
let z = (x, y) => x + y
//ES5 syntax
var z = function(x, y){
return x + y;
}
CODE SAMPLES
let greetAndPrintName = name => {
let str = `hello ${name}`;
console.log(str);
}
greetAndPrintName('vijay')
Example of a multi-line function; Wrapped with {}
let result = (x,y) => (
x + y
)
console.log(result(1,2)) //3
Example of a multi-line return statement; Wrapped with ()
ADDITIONAL NOTES ABOUT ARROW FUNCTIONS
- Arrow Functions have a "lexical" this value
- this usually refers to the object's context (where does it exist in scope)
- Typically in JS, when we add new variables they get attached to global scope, a function's scope, or in an object that they were created from -- context depends on how the variable gets created.
- We run into issues when we want to pass the value of this to another function, in these cases we use bind() in ES5 to bind the current value of this for use inside a nested function (aka var that = this).
- Arrow functions remember the context they were created in, so even as we pass this to nested functions, this is the same value as the arrow function it was created with, hence we don't need to worry about binding with arrow functions.
CONFUSING ES5
function Mammal(name) {
this.animalName = name || "Henry";
this.sayName = function() {
setTimeout(function() {
console.log("My name is " + this.animalName);
}, 200);
}
}
var dog = new Mammal();
dog.sayName();
//My name is undefined
How would we fix our code in ES5?
CONFUSING ES5
function Mammal(name) {
this.animalName = name || "Henry";
this.sayName = function() {
var that = this;
setTimeout(function() {
console.log("My name is " + that.animalName);
}, 200);
}
}
var dog = new Mammal();
dog.sayName();
//My name is Henry
One possible solution, use that.
Er.. um, this and that?
CONFUSING ES5
function Mammal(name) {
this.animalName = name || "Henry";
this.sayName = function() {
// var that = this;
setTimeout(function() {
console.log("My name is " + this.animalName);
}.bind(this), 200)
}
}
var dog = new Mammal();
dog.sayName();
//My name is Henry
How about bind instead?
NAH, LET'S JUST USE ARROW FUNCTIONS
class Mammal {
constructor(name = "Henry") {
this.animalName = name;
}
sayName() {
setTimeout(() => {
console.log(`My name is ${this.animalName}`)
}, 200)
}
}
let dog = new Mammal();
dog.sayName();
Yay, now I don't have to trip over my own code!
Remember, arrow functions remember their scope, they have a lexical scope binding.
REACTJS CODE WITH AND WITHOUT BINDING
import React, { Component } from "react";
import "./styles.css";
export default class App extends Component {
constructor() {
super();
this.clickMe = this.clickMe.bind(this); //headache -- use arrow functions instead!!!
}
clickMe() {
alert("hello you clicked me");
}
render() {
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<button onClick={this.clickMe}>Click Me</button>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
rootElement
);
Don't worry if you don't understand this right now, just the gist is fine.
REACTJS CODE WITH AND WITHOUT BINDING
import React, { Component } from "react";
import "./styles.css";
export default class App extends Component {
constructor() {
super();
}
render() {
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<button
onClick={() => {
/* Wow less code bloat!! */
alert("hello you clicked me");
}}
>
Click Me
</button>
</div>
);
}
}
Lexical arrow functions to the rescue!
Use them like super powers! Play around with the sandbox at:
Rest Operator
(...param) => param
Returns an array of arguments passed to function at run time.
Note, ...arg must always be the last argument, otherwise this will not work.
(...param, param2) //SyntaxError
(param, ...param2) //Correct
let sum = (total = 0, ...numbers) => {
numbers.forEach(num => total += num)
console.log(total);
}
sum(1,2,3,4,5,6,7);
sum(1,2,3,4,5,6);
sum(1,2,3);
Spread Operator
Spread operator plugs in values 1,2,3 into a,b,c respectively.
How would you do this in ES5?
The spread operator is used for arguments in a function call, while the rest operator is used as parameters in a function definition
let sum = (a,b,c) => {
console.log(a + b + c)
}
sum(...[1,2,3]) //spread operator
Objects Destructuring
What's the output of myVariable?
How about here?
Why do you think destructuring is valuable?
let { myVariable } = {
myVariable: "green Salad"
}
let [a,b] = [3, (x,y)=>x+y, 3]
Classes
How does this compare to prototypes?
Do you remember what a prototype is?
When is it better to use a class vs a function?
class Animal {
constructor(name="", species=""){
this.name = name
this.species = species
}
}
class Dog extends Animal {
constructor(name, species){
super(name,species)
}
bark(){
console.log(`${this.name}, a ${this.species} says woof woof`)
}
}
const fido = new Dog("Fido", "Dog")
fido.bark()
Promises
Avoid the common issue of "callback hell" in ES5; aka nested callbacks -- difficult to read and debug.
If you are doing work with RESTful services, use Promises instead
let p = new Promise((resolve, reject) => {
setTimeout(() => {
let rand = Math.floor((Math.random()*10),1), x = false
x = (rand % 2 == 0) ? true : x
console.log(rand)
x ? resolve() : reject()
}, 1000)
})
p.then(() => {
console.log(`Life's great`)
})
.catch(e => console.log(`You can't always get what you want`))
AJAX EXAMPLE
let ajaxP = (http) => {
return new Promise(function(resolve, reject) {
if (!http) {
throw new Error("Must define an http object")
}
let xhr = new XMLHttpRequest();
xhr.open(http.type, http.url);
xhr.onload = function() {
if (this.status >= 200 && this.status < 300) {
resolve(xhr.response);
} else {
reject({
status: this.status,
statusText: xhr.statusText
});
}
};
xhr.onerror = function() {
reject({
status: this.status,
statusText: xhr.statusText
})
}
xhr.send();
});
}
AJAX EXAMPLE (CONTINUED)
//Promisified version of our http request
ajaxP({
type: "GET",
url: "https://jsonplaceholder.typicode.com/todos/1"
}).then(response => {
return JSON.parse(response);
}).then(response => {
console.log(response)
let user = response.userId
let url = `https://jsonplaceholder.typicode.com/users/${user}`
return ajaxP({
type: "GET",
url: url
})
}).then(res => {
console.log(JSON.parse(res))
}).catch(e => console.log(e))
The basic pattern here is:
- Do something AJAXy
- Then do something else AJAXy... Then do something else AJAXy...
- Catch any errors :) -- is this a better pattern than callback hell?
FETCH (SIMPLIFIES AJAX & PROMISES)
fetch('https://jsonplaceholder.typicode.com/todos/1')
.then(response => response.json())
.then(json => console.log(json))
No need to write all the complexities of AJAX and error handling with XMLHttpRequest; the community already did it for you!
If you understand promises, fetch works exactly the same way. Check out the API!
You can replace JQuery $.ajax with fetch and avoid callback hell! Take the plunge today~
ASYNC & AWAIT
function promiseMeYoullWait() {
return new Promise((resolve, reject) => {
try {
setTimeout(() => {
resolve('You waited!')
}, 4000)
} catch (e) {
reject(`Sorry the program couldn't wait, Error: ${e}`)
}
})
}
//Start here when you read this code...
async function myAsync() {
let result = await promiseMeYoullWait()
console.log(result)
}
myAsync(); //You waited!
async & await allow us to write code that looks more understandable to a human; step by step, even though this code is still asynchronous -- it looks synchronous to us.
Why do you think this is important?
Iterators
What does it mean to be an iterable object?
What does it mean to iterate something?
ITERATOR CODE SAMPLE
let myArr = [1,2,3,4]
let itr = myArr.values() //get our iterator from our array
let i = itr.next(); //call the next method on the iterator
while(!i.done){
console.log(i); //show the value and done for the current object
i = itr.next(); //keep calling the iterator next method
}
An array is an iterable object; we can loop through it (it supports iteration)
An iterator helps us loop through the code, one step at a time. So think of a for loop that stops each time and gives you a result before going to next iteration.
Why might this be useful?
Generators
Generators yield.
Think a function that returns new results each time, rather than the same expected result over and over.
Generators return iterable objects, which give us access to new yield values
GENERATOR CODE SAMPLE
function* myGenerator() {
yield 1;
yield 2;
yield 3;
}
let itr = myGenerator(); //generator function call, creates a new iterator, itr
let res = itr.next(); //we get an object with { value: 1, done: false }
console.log(res.value, res.done); //1, false
// do something else
res = itr.next();
console.log(res.value, res.done); //2, false
// do something else
res = itr.next();
console.log(res.value, res.done); //3, true
A generator returns an iterable object with keys for value and a done flag. In each yield, we get the value and a done (true/false). So long as done is not true, we can keep iterating through the generator for new yield values
GENERATOR CODE SAMPLE
function* createNewId() {
let i = 0;
while (true) { //generate infinite ids
yield i;
i++;
}
}
let itr = createNewId() //create iterator
function createNewAccount(email) {
let res = itr.next(); //get next yield value
let id = res.value; //store yield value in id
let user = {
email,
id
}
console.log("new user created", user)
}
createNewAccount("aj@iw.org")
createNewAccount("ma@nn.com")
createNewAccount("pjw@sn.com")
This code can generate us new accounts forever, but we use iterators to control how often we need to yield
GENERATOR CODE SAMPLE
function* myGenerator(){
yield 1;
yield 2;
yield 3;
}
for(let i of myGenerator()){
console.log(i)
}
You can use the for...of loop for convenience (syntactic sugar) instead of calling myGenerator.next()
MAPS & SETS
Maps can hold values of any type and they also remember the order in which items were individually stored
Sets hold unique values; when do you think a Set might be useful over an array?
MAP EXAMPLE
//Map Example
let myMap = new Map();
myMap.set('John', 'Cheeseburger');
myMap.set('Vijay', 'Chicken Tikka Masala')
myMap.set('Danielle', 'Vegan Lasagna')
myMap.set('function doNothing(){}', 'Did nothing')
myMap.set(NaN, 'yup NaN works too')
console.log(myMap.size);
myMap.forEach((val, key) => console.log(`${key}: ${val}`))
The first argument is your "key", the second argument is your "value". Think of Maps like hashes, yet you can store any type you want as a key, and look it up in the same fashion to retrieve a value.
When do you think a Map might be better than a traditional object for item lookup?
SET EXAMPLE
let mySet = new Set();
//use the add method to add new values to our set
mySet.add(1)
mySet.add(2)
mySet.add((24/12))
mySet.add("iPhone")
//use the delete method to remove values from our set
mySet.delete(1)
console.log(mySet.has(2)) //true
mySet.forEach(val => console.log(val))
//2
//iPhone
console.log(mySet.size) //2
Sets must always have unique values, unlike arrays which can hold duplicate values. Why might a Set be better than an Array?
JavaScript Modules
- Before JavaScript Modules, how did one "import" and "export" code?
- What's the difference between AMD and CommonJS? Which one is more prevalent?
- How about UMD?
OLD WAY OF MODULING
//new Q library
var Q = (function(){
return {
log: function(query){
console.log(query)
},
ver: 1.0
};
}())
//version 2 update
(function(lib){
if(lib.ver < 2) {
update(lib);
}else {
console.log("Library at latest version")
return lib;
}
function update(lib){
lib.add = function(num1, num2){
lib.log(num1 + num2);
}
lib.subtract = function(num1, num2) {
lib.log(num1 - num2);
}
lib.ver = 2.0
}
return lib;
})(Q);
ES5 -- LET'S COMPOUND SCRIPTS! YAY
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<script type="text/javascript" src="Q.js"></script>
<script type="text/javascript" src="Q.update.js"></script>
<script type="text/javascript" src="Q.random.js"></script>
…
<script type="text/javascript" src="Q.update100.js"></script>
<script type="text/javascript" src="Q.plugin45.js"></script>
<script type="text/javascript" src="Q.randomN.js"></script>
</body>
</html>
Hmm.. this is getting heavy.. I wonder if our site will be okay with all these requests. Perhaps we could move to CDN? Could we minify all these into one file? Would that be optimal? What if we don't need all these libraries, should we just rewrite?
AMD TO RESCUE... MAYBE USING REQUIREJS
<html>
<head>
<title>My Sample Project</title>
<!-- data-main attribute tells require.js to load scripts/main.js after require.js loads. -->
<script data-main="scripts/main" src="scripts/require.js"></script>
</head>
The data-main attribute for the script tag tells require.js to load main.js after require.js loads. (The require.js library is downloaded from the RequireJS website).
The main.js file is where you can tell RequireJS to load in any other dependencies you need for your program. Modules are asynchronously loaded by RequireJS, hence Asynchronous Modular Development. Key benefit -- module loading for the browser (no NPM needed)
//Q.js file
define(["jquery"], function($) {
return {
log: function(val) {
$("body").html(val)
},
ver: 1.0
}
});
project folder >
| js
| lib
- Q.js
- jquery.js
- main.js
- require.js
index.html
HOW ABOUT COMMONJS INSTEAD? NPM?
/***** q.js *****/
function log(query) {
console.log(query)
}
var version = 1.0;
module.exports = {
log: log,
ver: version
};
- Code can now live in its own file and is scoped only to that file
- q.js lives in its own file, we add what we need to module.exports global, which node will pull into other files, like app.js
- app.js understands the require statement, and can get at the Q.js code.
- Using tools like grunt, gulp, we can minify, uglify all this code down into one file that our index.html uses. Key benefit -- easy workflow, can use with NPM ecosystem
/***** app.js *****/
var Q = require("Q.js");
console.log(Q.ver) //1.0
project folder >
| js
| q.js
| app.js
index.html
WHY NOT BOTH?
How many times have you seen this ad?
UMD - IF COMMONJS - USE IT, ELSE AMD, ELSE OLD WAY PLS.K.THX.BYE
(function(root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['Q'], factory);
} else if (typeof exports === 'object') {
// CommonJS
module.exports = factory(require('Q'));
} else {
// Browser globals
root.Q = factory(root.Q);
}
}(this, function(Q) {
Q.add = function(num1, num2) {
Q.log(num1 + num2);
};
return Q;
}));
Yeah, um.. no.. can we just go back to ES6 already?
ES6 GIVES YOU
- CommonJS syntax out of the box.
- Simpler/easier to read/write/execute
- import, export
- (replaces require, module.exports)
- Remember though, you still need to transpile ES6 code even with Node.js
- If you want to use AMD w/ES6 syntax instead, you can provided you transpile your code with Babel in a script tag (and include the Babel script first)
- However, all this said -- exporting/importing got a wholeeee lot easier!!! Thank you ES6 overlords!
ES6 IMPORT & EXPORT
/*** Car.js our Module file ***/
let car = "Toyota"
export default function driveMyCar() {
return `${car} is driving`
}
/*** index.js ***/
import driveMyCar from "Car"
let myCar = driveMyCar()
console.log(myCar)
Yay, so much simpler.. and we can use things like Webpack to bundle all our modules together, split the code into vendor files, our files, then cacahe redundant files, eliminate unused code, making our overall code easier to track/ship, generate less requests, and work on devices without much reliable internet giving our users a better user experience no matter where they are!!!!!!! (Okay sorry for making this complex again by mentioning Webpack lol).
See our talk on Webpack 4 on YouTube for more info (if interested in that rabbit hole)
ESNext
Let's use Decorators from Stage-2
Decorators are like functions that augment other functions (sometimes called wrappers).
You see them a lot in Angular syntax.
DECORATOR EXAMPLE
function log(User) {
return (...args) => {
console.log(`New user created ${args}`);
return new User(...args);
};
}
@log
class User {
constructor(name, age) {
console.log(`New user entered the chat: Name: ${name}, Age: ${age}`)
}
}
const u = new User('Graham', 34);
The idea with decorators is to make it so we can bury "startup" code elsewhere and just use "annotation" style syntax to augment classes with special powers -- in this case log every User
ANGULAR EXAMPLE OF USING DECORATORS
/* These are JavaScript import statements. Angular doesn’t know anything about these. */
import {
BrowserModule
} from '@angular/platform-browser';
import {
NgModule
} from '@angular/core';
import {
AppComponent
} from './app.component';
/* The @NgModule decorator lets Angular know that this is an NgModule. */
@NgModule({
declarations: [
AppComponent
],
imports: [ /* These are NgModule imports. */
BrowserModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule {}
Can you see how a decorator augments our AppModule class here?
Want more on Angular? Check out Angular presentation here.
Using TypeScript
TypeScript is a language offered by Microsoft that adds types to JavaScript; much like you have types in other programming languages like Python, C++, Java.
Common types: ints, booleans, floats, decimals, strings (stack variables), chars, and heap variables (objects). You can also use TS with ES6, React, etc by configuring a simple tsjson.config file
Types can be useful, especially when dealing with memory; you could hope that JS does its job, but you never know what monsters lurk under the hood.
Using TypeScript W/WEBPACK
Git repo set up for you to play around:
https://github.com/vijayxtreme/starterTSWebpack
Documentation on Config for Webpack and TS at:
https://webpack.js.org/guides/typescript/
Full blog post on TypeScript here:
SAMPLE TS CODE
class Student {
fullName: string;
constructor(public firstName: string, public middleInitial: string, public lastName: string) {
this.fullName = firstName + " " + middleInitial + " " + lastName;
}
}
interface Person {
firstName: string;
lastName: string;
}
function greeter(person: Person) {
return "Hello, " + person.firstName + " " + person.lastName;
}
let user = new Student("Jane", "M.", "User");
document.body.textContent = greeter(user);
Note the use of ES6 with types (stricter JS here)
Source: https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes.html
THANK YOU!
If you were overwhelmed (lol) and want to visit this again with me holding your hand even further, sign up for the video lecture at:
https://hackbuddy.teachable.com
(8 hours of video)
You can also get the book on Amazon.com (if you like reading):
Your purchases help keep the group going and generating more helpful content, lectures, meetups and interviews!
Copy of Guide to ES6
By DJTL
Copy of Guide to ES6
- 16