ES6-for-humans

by metagrover

metagrover /ES6-for-humans

A kickstarter guide to writing ES6

4.6K Stars 282 Forks Last release: Not found 66 Commits 0 Releases

Available items

No Items, yet!

The developer of this repository has not created any items for sale yet. Need a bug fixed? Help with integration? A different license? Create a request here:

ES6 for Humans


:loudspeaker: The complete guide is now available on Amazon

ES6 for humans - Apress book

Table of Contents


Languages


1. let, const and block scoping

let
allows you to create declarations which are bound to any block, called block scoping. Instead of using
var
, which provides function scope, it is recommended to use block scoped variables (
let
or
const
) in ES6.
var a = 2;
{
    let a = 3;
    console.log(a); // 3
    let a = 5; // TypeError: Identifier 'a' has already been declared
}
console.log(a); // 2

Another form of block-scoped declaration is the

const
, which creates constants. In ES6, a
const
represents a constant reference to a value. In other words,
Object
's and
Array
's contents may change, only the re-assignment of the variable is prevented. Here's a simple example:
{
    const b = 5;
    b = 10; // TypeError: Assignment to constant variable

const arr = [5, 6];
arr.push(7);
console.log(arr); // [5,6,7]
arr = 10; // TypeError: Assignment to constant variable
arr[0] = 3; // value is mutable
console.log(arr); // [3,6,7]

}

A few things to keep in mind:

  • Hoisting of
    let
    and
    const
    vary from the traditional hoisting of variables and functions. Both
    let
    and
    const
    are hoisted, but cannot be accessed before their declaration, because of Temporal Dead Zone
  • let
    and
    const
    are scoped to the nearest enclosing block.
  • When using const with fixed strings or values, CAPITAL_CASING might be appropriate (ex:
    const PI = 3.14
    )
  • const
    has to be defined with its declaration.
  • Always use
    const
    over
    let
    , unless you plan on re-assigning the variable.


2. Arrow Functions

Arrow functions are a short-hand notation for writing functions in ES6. The arrow function definition consists of a parameter list

( ... )
, followed by the
=>
marker and a function body. For single-argument functions, the parentheses may be omitted.
// Classical Function Expression
function addition(a, b) {
    return a + b;
};

// Implementation with arrow function const addition = (a, b) => a + b;

// With single argument, no parentheses required const add5 = a => 5 + a;

Note that in the above example, the

addition
arrow function is implemented with "concise body" which does not need an explicit return statement. Note the omitted
{ }
after the
=>
.

Here is an example with the usual "block body." Including the curly brace wrappers.

const arr = ['apple', 'banana', 'orange'];

const breakfast = arr.map(fruit => { return fruit + 's'; });

console.log(breakfast); // ['apples', 'bananas', 'oranges']

Behold! There is more...

Arrow functions don't just make the code shorter. They are closely related to

this
binding behavior.

Arrow functions behavior with

this
keyword varies from that of normal functions. Each function in JavaScript defines its own
this
context but arrow functions capture the
this
value of the nearest enclosing context. Check out the following code:
function Person() {
    // The Person() constructor defines `this` as an instance of itself.
    this.age = 0;

setInterval(function growUp() {
    // In non-strict mode, the growUp() function defines `this`
    // as the global object, which is different from the `this`
    // defined by the Person() constructor.
    this.age++;
}, 1000);

} var p = new Person();

In ECMAScript 3/5, this issue was fixed by assigning the value in

this
to a variable that could be closed over.
function Person() {
    const self = this;
    self.age = 0;

setInterval(function growUp() {
    // The callback refers to the `self` variable of which
    // the value is the expected object.
    self.age++;
}, 1000);

}

As mentioned above, arrow functions capture the this value of the nearest enclosing context, so the following code works as expected, even with nested arrow functions.

function Person() {
    this.age = 0;

setInterval(() => {
    setTimeout(() => {
        this.age++; // `this` properly refers to the person object
    }, 1000);
}, 1000);

}

let p = new Person();

Read more about 'Lexical this' in arrow functions here


3. Default Function Parameters

ES6 allows you to set default parameters in function definitions. Here is a simple illustration.

const getFinalPrice = (price, tax = 0.7) => price + price * tax;
getFinalPrice(500); // 850


4. Spread / Rest Operator

...
operator is referred to as spread or rest operator, depending on how and where it is used.

When used with any iterable, it acts as to "spread" it into individual elements:

const makeToast = (breadType, topping1, topping2) => {
  return `I had ${breadType} toast with ${topping1} and ${topping2}`;
};
const ingredients = ['wheat', 'butter', 'jam'];
makeToast(...ingredients);
// "I had wheat toast with butter and jam"

makeToast(...['sourdough', 'avocado', 'kale']); // "I had sourdough toast with avocado and kale"

Spread is also great for shaping a new object from other object(s):

const defaults = {avatar: 'placeholder.jpg', active: false}
const userData = {username: 'foo', avatar: 'bar.jpg'}

console.log({created: '2017-12-31', ...defaults, ...userData}) // {created: "2017-12-31", avatar: "bar.jpg", active: false, username: "foo"}

New arrays can also be shaped expressively:

const arr1 = [1, 2, 3];
const arr2 = [7, 8, 9];
console.log([...arr1, 4, 5, 6, ...arr2]) // [1, 2, 3, 4, 5, 6, 7, 8, 9]

The other common usage of

...
is gathering all arguments together into an array. This is referred as "rest" operator.
function foo(...args) {
    console.log(args);
}
foo(1, 2, 3, 4, 5); // [1, 2, 3, 4, 5]


5. Object Literal Extensions

ES6 allows declaring object literals by providing shorthand syntax for initializing properties from variables and defining function methods. It also enables the ability to have computed property keys in an object literal definition.

function getCar(make, model, value) {
    return {
        // with property value shorthand
        // syntax, you can omit the property
        // value if key matches variable
        // name
        make,  // same as make: make
        model, // same as model: model
        value, // same as value: value

    // computed values now work with
    // object literals
    ['make' + make]: true,

    // Method definition shorthand syntax
    // omits `function` keyword & colon
    depreciate() {
        this.value -= 2500;
    }
};

}

let car = getCar('Kia', 'Sorento', 40000); console.log(car); // { // make: 'Kia', // model:'Sorento', // value: 40000, // makeKia: true, // depreciate: function() // }


6. Octal and Binary Literals

ES6 has new support for octal and binary literals. Prependending a number with

0o
or
0O
would convert it into octal value. Have a look at the following code:
let oValue = 0o10;
console.log(oValue); // 8

let bValue = 0b10; // 0b or 0B for binary console.log(bValue); // 2


7. Array and Object Destructuring

Destructuring helps in avoiding the need for temp variables when dealing with object and arrays.

function foo() {
    return [1, 2, 3];
}
let arr = foo(); // [1,2,3]

let [a, b, c] = foo(); console.log(a, b, c); // 1 2 3

function getCar() {
  return {
    make: 'Tesla',
    model: 'g95',
    metadata: {
      vin: '123abc',
      miles: '12000'
    }
  };
}

const {make, model} = getCar(); console.log(make, model); // Tesla g95

const {make, metadata: {miles}} = getCar(); console.log(make, miles); // Tesla 12000


8. super in Objects

ES6 allows to use

super
method in (classless) objects with prototypes. Following is a simple example:
const parent = {
    foo() {
        console.log("Hello from the Parent");
    }
}

const child = { foo() { super.foo(); console.log("Hello from the Child"); } }

Object.setPrototypeOf(child, parent); child.foo(); // Hello from the Parent // Hello from the Child


9. Template Literal and Delimiters

ES6 introduces an easier way to add interpolations which are evaluated automatically.

  • `${ ... }` is used for rendering the variables.
  • ` Backtick is used as delimiter.
let user = 'Kevin';
console.log(`Hi ${user}!`); // Hi Kevin!


10. for...of vs for...in

  • for...of
    iterates over iterable objects, such as array.
const nicknames = ['di', 'boo', 'punkeye'];
nicknames.size = 3;
for (let nickname of nicknames) {
    console.log(nickname);
}
// di
// boo
// punkeye
  • for...in
    iterates over all enumerable properties of an object.
const nicknames = ['di', 'boo', 'punkeye'];
nicknames.size = 3;
for (let nickname in nicknames) {
    console.log(nickname);
}
// 0
// 1
// 2
// size


11. Map and WeakMap

ES6 introduces new set of data structures called

Map
and
WeakMap
. Now, we actually use maps in JavaScript all the time. In fact every object can be considered as a
Map
.

An object is made of keys (always strings) and values, whereas in

Map
, any value (both objects and primitive values) may be used as either a key or a value. Have a look at this piece of code:
const myMap = new Map();

const keyString = "a string", keyObj = {}, keyFunc = () => {};

// setting the values myMap.set(keyString, "value associated with 'a string'"); myMap.set(keyObj, "value associated with keyObj"); myMap.set(keyFunc, "value associated with keyFunc");

myMap.size; // 3

// getting the values myMap.get(keyString); // "value associated with 'a string'" myMap.get(keyObj); // "value associated with keyObj" myMap.get(keyFunc); // "value associated with keyFunc"

WeakMap

A

WeakMap
is a Map in which the keys are weakly referenced, that doesn’t prevent its keys from being garbage-collected. That means you don't have to worry about memory leaks.

Another thing to note here- in

WeakMap
as opposed to
Map
every key must be an object.

A

WeakMap
only has four methods
delete(key)
,
has(key)
,
get(key)
and
set(key, value)
.
const w = new WeakMap();
w.set('a', 'b');
// Uncaught TypeError: Invalid value used as weak map key

const o1 = {}, o2 = () => {}, o3 = window;

w.set(o1, 37); w.set(o2, "azerty"); w.set(o3, undefined);

w.get(o3); // undefined, because that is the set value

w.has(o1); // true w.delete(o1); w.has(o1); // false


12. Set and WeakSet

Set objects are collections of unique values. Duplicate values are ignored, as the collection must have all unique values. The values can be primitive types or object references.

const mySet = new Set([1, 1, 2, 2, 3, 3]);
mySet.size; // 3
mySet.has(1); // true
mySet.add('strings');
mySet.add({ a: 1, b:2 });

You can iterate over a set by insertion order using either the

forEach
method or the
for...of
loop.
mySet.forEach((item) => {
    console.log(item);
    // 1
    // 2
    // 3
    // 'strings'
    // Object { a: 1, b: 2 }
});

for (let value of mySet) { console.log(value); // 1 // 2 // 3 // 'strings' // Object { a: 1, b: 2 } }

Sets also have the

delete()
and
clear()
methods.

WeakSet

Similar to

WeakMap
, the
WeakSet
object lets you store weakly held objects in a collection. An object in the
WeakSet
occurs only once; it is unique in the WeakSet's collection.
const ws = new WeakSet();
const obj = {};
const foo = {};

ws.add(window); ws.add(obj);

ws.has(window); // true ws.has(foo); // false, foo has not been added to the set

ws.delete(window); // removes window from the set ws.has(window); // false, window has been removed


13. Classes in ES6

ES6 introduces new class syntax. One thing to note here is that ES6 class is not a new object-oriented inheritance model. They just serve as a syntactical sugar over JavaScript's existing prototype-based inheritance.

One way to look at a class in ES6 is just a new syntax to work with prototypes and constructor functions that we'd use in ES5.

Functions defined using the

static
keyword implement static/class functions on the class.
class Task {
    constructor() {
        console.log("task instantiated!");
    }

showId() {
    console.log(23);
}

static loadAll() {
    console.log("Loading all tasks..");
}

}

console.log(typeof Task); // function const task = new Task(); // "task instantiated!" task.showId(); // 23 Task.loadAll(); // "Loading all tasks.."

extends and super in classes

Consider the following code:

class Car {
    constructor() {
        console.log("Creating a new car");
    }
}

class Porsche extends Car { constructor() { super(); console.log("Creating Porsche"); } }

let c = new Porsche(); // Creating a new car // Creating Porsche

extends
allow child class to inherit from parent class in ES6. It is important to note that the derived constructor must call
super()
.

Also, you can call parent class's method in child class's methods using

super.parentMethodName()

Read more about classes here

A few things to keep in mind:

  • Class declarations are not hoisted. You first need to declare your class and then access it, otherwise ReferenceError will be thrown.
  • There is no need to use
    function
    keyword when defining functions inside a class definition.


14. Symbol

A

Symbol
is a unique and immutable data type introduced in ES6. The purpose of a symbol is to generate a unique identifier but you can never get any access to that identifier.

Here’s how you create a symbol:

const sym = Symbol("some optional description");
console.log(typeof sym); // symbol

Note that you cannot use

new
with
Symbol(…)
.

If a symbol is used as a property/key of an object, it’s stored in a special way that the property will not show up in a normal enumeration of the object’s properties.

const o = {
    val: 10,
    [Symbol("random")]: "I'm a symbol",
};

console.log(Object.getOwnPropertyNames(o)); // val

To retrieve an object’s symbol properties, use

Object.getOwnPropertySymbols(o)


15. Iterators

An iterator accesses the items from a collection one at a time, while keeping track of its current position within that sequence. It provides a

next()
method which returns the next item in the sequence. This method returns an object with two properties: done and value.

ES6 has

Symbol.iterator
which specifies the default iterator for an object. Whenever an object needs to be iterated (such as at the beginning of a for..of loop), its @@iterator method is called with no arguments, and the returned iterator is used to obtain the values to be iterated.

Let’s look at an array, which is an iterable, and the iterator it can produce to consume its values:

const arr = [11,12,13];
const itr = arr[Symbol.iterator]();

itr.next(); // { value: 11, done: false } itr.next(); // { value: 12, done: false } itr.next(); // { value: 13, done: false }

itr.next(); // { value: undefined, done: true }

Note that you can write custom iterators by defining

obj[Symbol.iterator]()
with the object definition.


16. Generators

Generator functions are a new feature in ES6 that allow a function to generate many values over time by returning an object which can be iterated over to pull values from the function one value at a time.

A generator function returns an iterable object when it's called. It is written using the new

*
syntax as well as the new
yield
keyword introduced in ES6.
function *infiniteNumbers() {
    let n = 1;
    while (true) {
        yield n++;
    }
}

const numbers = infiniteNumbers(); // returns an iterable object

numbers.next(); // { value: 1, done: false } numbers.next(); // { value: 2, done: false } numbers.next(); // { value: 3, done: false }

Each time yield is called, the yielded value becomes the next value in the sequence.

Also, note that generators compute their yielded values on demand, which allows them to efficiently represent sequences that are expensive to compute, or even infinite sequences.


17. Promises

ES6 has native support for promises. A promise is an object that is waiting for an asynchronous operation to complete, and when that operation completes, the promise is either fulfilled(resolved) or rejected.

The standard way to create a Promise is by using the

new Promise()
constructor which accepts a handler that is given two functions as parameters. The first handler (typically named
resolve
) is a function to call with the future value when it's ready; and the second handler (typically named
reject
) is a function to call to reject the Promise if it can't resolve the future value.
const p = new Promise((resolve, reject) => {
    if (/* condition */) {
        resolve(/* value */);  // fulfilled successfully
    } else {
        reject(/* reason */);  // error, rejected
    }
});

Every Promise has a method named

then
which takes a pair of callbacks. The first callback is called if the promise is resolved, while the second is called if the promise is rejected.
p.then((val) => console.log("Promise Resolved", val),
       (err) => console.log("Promise Rejected", err));

Returning a value from

then
callbacks will pass the value to the next
then
callback.
const hello = new Promise((resolve, reject) => { resolve("Hello") });

hello.then((str) => ${str} World) .then((str) => ${str}!) .then((str) => console.log(str)) // Hello World!

When returning a promise, the resolved value of the promise will get passed to the next callback to effectively chain them together. This is a simple technique to avoid "callback hell".

const p = new Promise((resolve, reject) => { resolve(1) });

const eventuallyAdd1 = (val) => new Promise((resolve, reject) => { resolve(val + 1) });

p.then(eventuallyAdd1) .then(eventuallyAdd1) .then((val) => console.log(val)); // 3

We use cookies. If you continue to browse the site, you agree to the use of cookies. For more information on our use of cookies please see our Privacy Policy.