ES6 Walkthrough

Andrei Antal

JSHacks Sessions

Contents

  1. JavaScript History
  2. Block scope variables
  3. Template strings
  4. Enhanced objects
  5. Symbols
  6. Arrow functions
  7. Classes
  8. Set and Map
  9. Destructuring
  10. Spread and rest
  11. Modules
  12. Iterators and Generators
  13. Other ES6 features

BUT FIRST...

Who are we

JAVASCRIPT HISTORY

JavaScript History

  • 1991
    • CERN - Tim Berners-Lee - WWW
  • 1993
    • Mosaic - the first modern browser
  • 1994
    • Mosaic Netscape (codename Mozilla - Mosaic killer)
    • Renamed to Netscape Navigator
  • 1995
    • Brendan Eich - Mocha - first prototype version of JavaScript (done in only 10 days!)
    • It first shipped as LiveScript than renamed to JavaScript
  • 1996
    • JScript - Microsoft's reversed engineered version of JavaScript shipped with Internet Explorer 3
    • beginning of the browser wars
    • Netscape takes JavaScript to ECMA for standardization
  • 1997
    • ECMA-262 (ECMAScript)
  • ​1998
    • ​ECMAScript 2
  • ​1999
    • ​ECMAScript 3

JavaScript History

  • 2004
    • Firefox was released by Mozilla
  • 2005
    • Mozilla (Brendan Eich) and Macromedia began to work on ECMAScript 4 => ActionScript 3
    • ES4 was never completed
    • Ajax is invented
  • ​2007
    • ​ECMAScript 3.1
  • 2009
    • ES5 emerges from previous discussions

JavaScript History

  • ​2011
    • ​ES5.1​
  • 2015​
    • June - ES6 (ES2015)
  • 2016
    • June - ES2016

To be continued...

JavaScript History

BLOCK SCOPE VARIABLES

Block scope variables

function getColor(condition) {

    if (condition) {
        var value = "blue";

        // other code
        return value;
    } else {

        return null;
    }
}

ES5 variable hoisting

function getColor(condition) {
    var value; // undefined

    if (condition) {
        value = "blue";
        // other code
        return value;
    } else {
        return null;
    }
}

Block scope variables

function getColor(condition) {

    if (condition) {
        var value = "blue";

        // ...

        return value;
    } else {

        // value = undefined

        return null;
    }

    // value = undefined
}

ES5 variable hoisting

  • variables are hoisted to the top of the enclosing function

Block scope variables


  for (var i = 0; i < 10; i++) {
      process(items[i]);
  }

ES5 variables in loops

var funcs = [];

for (var i = 0; i < 10; i++) {
    funcs.push(function() { console.log(i); });
}

ES5 functions in loops

console.log(i); // 10
funcs.forEach(function(func) {
    func();     // 10, 10, 10.... 
});

Block scope variables

  • Block scope declarations - variables that are inaccessible outside of a given block scope

 

  • Block scopes (lexical scopes)
    • Inside of a function
    • Inside of a block (between { and } )

 

  • ES6 introduces 2 new type of variable declarations:
    • let
    • const

Block scope variables

The LET declarations

function getColor(condition) {

    if (condition) {
        let value = "blue";

        // other code

        return value;
    } else {

        // value doesn't exist here - ReferenceError
        return null;
    }
    // value doesn't exist here - ReferenceError
}
  • The variable is only accessible inside its enclosing block
  • A ReferenceError is thrown if we try to access the variable outside of its scope

Block scope variables

Redeclarations

var count = 30;

// Syntax error
let count = 40;
  • let identifier cannot be redefined inside a scope
  • It's ok to make a let declaration if it creates a new variable in the scope
var count = 30;

// Does not throw an error
if (condition) {

    let count = 40; // shadows the outer variable inside this scope

    // more code
}
  • a new count variable is created that shadows the former

Block scope variables

The CONST declarations

// Valid constant
const maxItems = 30;

// Syntax error: missing initialization
const name;
  • Variables of which the content (or reference) does not change (not necessarily immutable) 
  • For this reason they need to be initialized at declaration
  • cannot redeclare variables in the same scope
var message = "Hello!";
let age = 25;

// Each of these would throw an error.
const message = "Goodbye!";
const age = 30;
const maxItems = 5;

maxItems = 6;      // throws error

Block scope variables

The CONST declarations

const person = {
    name: "Andrei"
};

// works
person.name = "George";

// throws an error - reinitialization
person = {
    name: "George"
};
  • A const declaration prevents modification of the binding and not of the value itself.

Block scope variables

Temporal Dead Zone (TDZ)

if (condition) {
    console.log(typeof value);  // ReferenceError!
    let value = "blue";
}
  • A variable declared with either let or const cannot be accessed until after the declaration.
console.log(typeof value);     // "undefined"

if (condition) {
    let value = "blue";
}

Block scope variables

Loop behaviour - let

for (let i = 0; i < 10; i++) {
    process(items[i]);
}
var funcs = [];

for (let i = 0; i < 10; i++) {
    funcs.push(function() {
        console.log(i);
    });
}
  • The let declaration creates a new variable i each time , so each function created inside the loop gets its own copy of i
// i is not accessible here - throws an error
console.log(i);
funcs.forEach(function(func) {
    func();     // 0, 1, 2, 3,...
})

Block scope variables

Loop behaviour - const

var funcs = [];

// throws an error after one iteration
for (const i = 0; i < 10; i++) {
    funcs.push(function() {
        console.log(i);
    });
}
var funcs = [],
    object = {
        a: true,
        b: true,
        c: true
    };

// doesn't cause an error
for (const key in object) {
    funcs.push(function() {
        console.log(key);
    });
}

TEMPLATE STRINGS

Template strings

New string methods :

  • includes() - check if the given text is found anywhere within the string.

 

  • startsWith() - check if the given text is found at the beginning of the string

 

  • endsWith() - check if the given text is found at the end of the string

 

  • repeat() -  returns a new string containing the original string repeated the specified number of times.

Template strings

New string methods

var msg = "Hello world!";

console.log(msg.startsWith("Hello"));       // true
console.log(msg.endsWith("!"));             // true
console.log(msg.includes("o"));             // true

console.log(msg.startsWith("o"));           // false
console.log(msg.endsWith("world!"));        // true
console.log(msg.includes("x"));             // false

console.log(msg.startsWith("o", 4));        // true
console.log(msg.endsWith("o", 8));          // true
console.log(msg.includes("o", 8));          // false

console.log("x".repeat(3));         // "xxx"
console.log("hello".repeat(2));     // "hellohello"
console.log("abc".repeat(4));       // "abcabcabcabc"

Template strings

Template literals

 

  • Multiline strings A formal concept of multiline strings.

 

  • Basic string formatting The ability to substitute parts of the string for values contained in variables.

 

  • HTML escaping The ability to transform a string such that it is safe to insert into HTML.

Template strings

Template literals

  • strings delimited by backticks (`) instead of double or single quotes.
let message = `Hello world!`;

console.log(message);               // "Hello world!"
console.log(typeof message);        // "string"
console.log(message.length);        // 12

Template strings

Multiline strings

// old syntax bug
var message = "Multiline \   
string";

console.log(message);       // "Multiline string"
  • ES5 multiline strings
var message = "Multiline \n\
string";

console.log(message);       // "Multiline
                            //  string"
var message = [
    "Multiline ",
    "string"
].join("\n");

let message = "Multiline \n" +
    "string";

Template strings

Multiline strings

let message = `Multiline
               string`;

console.log(message);           // "Multiline
                                //                 string"
console.log(message.length);    // 31
  • Preserve newlines
  • Preserve tabs
let message = `Multiline
string`;

console.log(message);           // "Multiline
                                //  string"
console.log(message.length);    // 16

Template strings

Multiline strings

  • Better HTML strings
    • ES5
let message = `
<table>
    <tr>Jane</tr>
    <tr>Bond&</tr>
    <tr>Lars</tr>
    <tr>Croft</tr>
</table>`;


var message = "<table>" +
    "<tr>Jane</tr>" +
    "<tr>Bond&</tr>" +
    "<tr>Lars</tr>" +
    "<tr>Croft</tr>" +
"</table>";


  • ES6

Template strings

Substitutions

  • Substitutions are delimited by ${ and } and can have any JavaScript expression inside.
let name = "Andrei",
    message = `Hello, ${name}.`;

console.log(message);       // "Hello, Andrei."
let count = 10,
    price = 0.25,
    message = `${count} items cost $${(count * price).toFixed(2)}.`;

console.log(message);       // "10 items cost $2.50."

Template strings

Tagged Templates

let message = tag`Hello world`;

function tag(literals, ...substitutions) {
    // return a string
}
  • literals - an array containing the literal strings as interpreted by JavaScript
  • substitutions - the interpreted value of each substitution
  • perform a transformation on the template literal and returns the final string value​

Template strings

Tagged Templates

let count = 10,
    price = 0.25,
    message = passthru`${count} items cost $${(count * price).toFixed(2)}.`;

literals

  • The empty string before the first substitution ("")
  • The string after the first substitution and before the second (" items cost $")
  • The string after the second substitution (".")

substitutions

  • ​10 (count)
  • 2.25 ( (count*price).toFixed(2) )

Template strings

Tagged Templates

let message = tag`Hello world`;

function tag(literals, ...substitutions) {
    // return a string
}

ENHANCED OBJECTS

Enhanced objects

Object Literal Syntax Extensions

 

  • Property Initializer Shorthand
    • ES5
function createPerson(name, age) {
    return {
        name: name,
        age: age
    };
}
    • ES6
function createPerson(name, age) {
    return {
        name,
        age
    };
}

Enhanced objects

Object Literal Syntax Extensions

 

  • Concise Methods
    • ES5
var person = {
    name: "Nicholas",
    sayName: function() {
        console.log(this.name);
    }
};
    • ES6
var person = {
    name: "Nicholas",
    sayName() {
        console.log(this.name);
    }
};

Enhanced objects

Object Literal Syntax Extensions

  • Formal Method Definition
let person = {

    // method
    getGreeting() {
        return "Hello";
    }
};

// not a method
function shareGreeting() {
    return "Hi!";
}
  • A method is a function that has an internal [[HomeObject]] property containing the object to which the method belongs

Enhanced objects

Object Literal Syntax Extensions

 

  • Using super() for prototype calls
let person = {
    getGreeting() {
        return "Hello";
    }
};

// prototype is person
let friend = {
    getGreeting() {
        return super.getGreeting() + ", hi!";
    }
};
Object.setPrototypeOf(friend, person);

console.log(friend.getGreeting());  // "Hello, hi!"

Enhanced objects

Object Literal Syntax Extensions

 

  • Computed property names
    • ES5
var person = {},
    lastName = "last name";

person["first name"] = "Andrei";
person[lastName] = "Antal";

console.log(person["first name"]);      // "Andrei"
console.log(person[lastName]);          // "Antal"
var person = {
    "first name": "Andrei"
};

console.log(person["first name"]);      // "Andrei"
var suffix = " name";
var person = {};

person["first" + suffix] = "Andrei";

Enhanced objects

Object Literal Syntax Extensions

 

  • Computed property names
    • ES6
var lastName = "last name";

var person = {
    "first name": "Andrei",
    [lastName]: "Antal"
};

console.log(person["first name"]);      // "Andrei"
console.log(person["last name"]);          // "Antal"
var suffix = " name";

var person = {
    ["first" + suffix]: "Andrei",
    ["last" + suffix]: "Antal"
};

console.log(person["first name"]);      // "Andrei"
console.log(person["last name"]);       // "Antal"

Enhanced objects

New methods

  • Object.is() - fix the remaining quirks of the identically equals operator
console.log(+0 == -0);              // true
console.log(+0 === -0);             // true
console.log(Object.is(+0, -0));     // false
console.log(NaN == NaN);            // false
console.log(NaN === NaN);           // false
console.log(Object.is(NaN, NaN));   // true
console.log(5 == 5);                // true
console.log(5 == "5");              // true
console.log(5 === 5);               // true
console.log(5 === "5");             // false
console.log(Object.is(5, 5));       // true
console.log(Object.is(5, "5"));     // false

Enhanced objects

New methods

  • Object.assign() - copy the values of all enumerable own properties from one or more source objects to a target object

Object.assign(target, ...sources)
  • Merging objects
var o1 = { a: 1 };
var o2 = { b: 2 };
var o3 = { c: 3 };

var obj = Object.assign(o1, o2, o3);
console.log(obj); // { a: 1, b: 2, c: 3 }
console.log(o1);  // { a: 1, b: 2, c: 3 }, target object itself is changed.

Enhanced objects

Object.assign()

  • Merging objects with same properties
var o1 = { a: 1, b: 1, c: 1 };
var o2 = { b: 2, c: 2 };
var o3 = { c: 3 };

var obj = Object.assign({}, o1, o2, o3);
console.log(obj); // { a: 1, b: 2, c: 3 }
  • Cloning object
var obj = { a: 1 };
var copy = Object.assign({}, obj);
console.log(copy); // { a: 1 }

SYMBOLS

Symbols

Symbols - a new type of primitive in JavaScript used for adding non-string values for property names (they don't have a literal form)

  • Since symbols are primitives they are not created using new Symbol()
let firstName = Symbol("first name");    // Debugging purposes only
let person = {};

person[firstName] = "Andrei";

console.log("first name" in person);        // false
console.log(person[firstName]);             // "Andrei"
console.log(firstName);                     // "Symbol(first name)"
let firstName = Symbol();
let person = {};

person[firstName] = "Andrei";
console.log(person[firstName]);     // "Andrei"
console.log(typeof firstName);      // "symbol"

Symbols

Symbols cannot be coerced into strings or numbers

let uid = Symbol.for("uid"),
    desc = String(uid);

console.log(desc);              // "Symbol(uid)"
let uid = Symbol.for("uid"),
    desc = uid + "";            // error!
let uid = Symbol.for("uid"),
    sum = uid / 1;            // error!

Symbols

Sharing Symbols

  • ES6 provides a global symbol registry

  • Create symbols using the .for(lookupName) method
let uid = Symbol.for("uid");
let object = {
    [uid]: "12345"
};

console.log(object[uid]);       // "12345"
console.log(uid);               // "Symbol(uid)"

let uid2 = Symbol.for("uid");

console.log(uid === uid2);      // true
console.log(object[uid2]);      // "12345"
console.log(uid2);              // "Symbol(uid)"
  • The global symbol registry is a shared environment, just like the global scope

Symbols

Sharing Symbols

  • retrieve the key associated with a symbol in the global symbol registry by calling the Symbol.keyFor() method
let uid = Symbol.for("uid");
console.log(Symbol.keyFor(uid));    // "uid"

let uid2 = Symbol.for("uid");
console.log(Symbol.keyFor(uid2));   // "uid"

let uid3 = Symbol("uid");
console.log(Symbol.keyFor(uid3));   // undefined

Symbols

Well-known symbols

  • Symbol.hasInstance
  • Symbol.isConcatSpreadable
  • Symbol.iterator
  • Symbol.match
  • Symbol.replace
  • Symbol.search
  • Symbol.species
  • Symbol.split
  • Symbol.toPrimitive
  • Symbol.toStringTag 
  • Symbol.unscopables

Symbols

  • Symbol.hasInstance

obj instanceof Array;

Array[Symbol.hasInstance](obj);
  • Symbol.toStringTag

obj instanceof Array;

Object.prototype.toString()

ARROW FUNCTIONS

Arrow functions

  • Functions defined with a new syntax that uses an “arrow” (=>)
  • Difference from "classic" functions
    • Cannot be called with new -  no new.target binding
    • No prototype - no super binding
    • Can’t change this
    • No arguments object
    • No duplicate named parameters
  • The value of this, super, arguments, and new.target inside of the function is by the closest containing non-arrow function.

Arrow functions

Arrow function syntax


var reflect = value => value;
var reflect = (value) => { return value };

// ES5 equivalent function

var reflect = function(value) {
    return value;
};
  • no parenthesis if only 1 argument

var reflect = (value) => { return value };
  • no return or {} pair if only one statement

Arrow functions

Arrow function syntax

var getName = () => "Andrei";

// ES5 equivalent function

var getName = function() {
    return "Andrei";
};
var sum = (num1, num2) => num1 + num2;

// ES5 equivalent function

var sum = function(num1, num2) {
    return num1 + num2;
};
  • paranthesis needed for 2 or more arguments
  • for no arguments, () is requires

Arrow functions

Arrow function syntax

var getTempItem = id => ({ id: id, name: "Temp" });

// effectively equivalent to:

var getTempItem = function(id) {

    return {
        id: id,
        name: "Temp"
    };
};
  • if we return the object, we need to wrap it in ( )
var comparator = (a, b) => a - b;

console.log(typeof comparator);                 // "function"
console.log(comparator instanceof Function);    // true
  • arrow functions are still functions

Arrow functions

Binding to this

var PageHandler = {

    id: "123456",

    init: function() {
        document.addEventListener("click", function(event) {
            this.doSomething(event.type);
        }, false);
    },

    doSomething: function(type) {
        console.log("Handling " + type  + " for " + this.id);
    }
};

PageHandler.init();
  • event listeners usually bind this to the target of the event

Arrow functions

Binding to this

var PageHandler = {

    id: "123456",

    init: function() {
        document.addEventListener("click", (function(event) {
            this.doSomething(event.type);     // no error
        }).bind(this), false);
    },

    doSomething: function(type) {
        console.log("Handling " + type  + " for " + this.id);
    }
};

PageHandler.init();
  • we can change the this binding (among other ways) by using the .bind() method

Arrow functions

Binding to this

function UiComponent {
    var that = this;
    var button = document.getElementById(#myButton);
    button.addEventListener(click,
        function () {
            console.log(CLICK);
            that.handleClick();
        });
    }

UiComponent.prototype.handleClick = function () { ... };
  • we can also keep a reference to this

Arrow functions

Binding to this

var PageHandler = {

    id: "123456",

    init: function() {
        document.addEventListener("click",
                event => this.doSomething(event.type), false);
    },

    doSomething: function(type) {
        console.log("Handling " + type  + " for " + this.id);
    }
};
  • arrow functions don't bind to this - the value of this inside an arrow function can only be determined by looking up the scope chain

Arrow functions

Arrow function in array methods


var arr = [5, 6, 13, 0, 1, 18, 23];
var sum = arr.reduce((a, b) => a + b);  // 66

var even = arr.filter(v => v % 2 == 0); // [6, 0, 18]

var double = arr.map(v => v * 2);       // [10, 12, 26, 0, 2, 36, 46]
var sum = arr.reduce(function (a, b) {
    return a + b;
});  // 66

var even = arr.filter(function (v) { 
    return v % 2 == 0;
} // [6, 0, 18]

var double = arr.map(function (v){
    return v * 2;
)}       // [10, 12, 26, 0, 2, 36, 46]
  • ES5 methods
  • Arrow functions

CLASSES

Classes

ES5 way of making "classes"

function Person(name) {
    this.name = name;
}

PersonType.prototype.sayName = function() {
    console.log(this.name);
};

let person = new Person("Andrei");
person.sayName();   // outputs "Andrei"

console.log(person instanceof Person);      // true
console.log(person instanceof Object);      // true
console.log(typeof Person);                 // "function"

Classes

Prototypes

Classes

ES6 Classes

class PersonClass {

    constructor(name) {     // same as Person constructor function
        this.name = name;
    }

    sayName() {             // same as Person.prototype.sayName
        console.log(this.name);
    }
}

let person = new PersonClass("Andrei");
person.sayName();   // outputs "Andrei"

console.log(person instanceof PersonClass);     // true
console.log(person instanceof Object);          // true

console.log(typeof PersonClass);                    // "function"
console.log(typeof PersonClass.prototype.sayName);  // "function"
  • Own properties (properties that occur on the instance rather than the prototype) can only be created inside a class constructor or method.

Classes

ES5 equivalent of a class

let Person = (function() {
    "use strict";
    const Person = function(name) {
        if (typeof new.target === "undefined") {
            throw new Error("Constructor must be called with new.");
        }

        this.name = name;
    }

    Object.defineProperty(Person.prototype, "sayName", {
        value: function() {
            if (typeof new.target !== "undefined") {
                throw new Error("Method cannot be called with new.");
            }

            console.log(this.name);
        },
        enumerable: false,
        writable: true,
        configurable: true
    });

    return Person;
}());

Classes

Differences between classes and functions:

  • Class declarations, unlike function declarations, are not hoisted
  • All code inside of class declarations runs in strict mode automatically
  • All methods are non-enumerable
  • All methods will throw an error if you try to call them with new
  • Calling the class constructor without new throws an error
  • Attempting to overwrite the class name within a class method throws an error

Classes

Class expressions

  • Anonymus classes
let PersonClass = class {

    // equivalent of the PersonType constructor
    constructor(name) {
        this.name = name;
    }

    // equivalent of PersonType.prototype.sayName
    sayName() {
        console.log(this.name);
    }
};

let person = new PersonClass("Andrei");
person.sayName();                               // "Andrei"

console.log(person instanceof PersonClass);     // true
console.log(person instanceof Object);          // true

console.log(typeof PersonClass);                    // "function"
console.log(typeof PersonClass.prototype.sayName);  // "function"

Classes

Class expressions

  • Named class expressions
let PersonClass = class PersonClass2 {

    // equivalent of the PersonType constructor
    constructor(name) {
        this.name = name;
    }

    // equivalent of PersonType.prototype.sayName
    sayName() {
        console.log(this.name);
    }
};

console.log(typeof PersonClass);        // "function"
console.log(typeof PersonClass2);       // "undefined"

Classes

Accessor properties

class Person {

    constructor(name) {
        this.name = name;
    }

    get name() {
        return this.name;
    }

    set name(value) {
        this.name = value;
    }
}

var descriptor = Object.getOwnPropertyDescriptor(Person.prototype, "name");
console.log("get" in descriptor);   // true
console.log("set" in descriptor);   // true
console.log(descriptor.enumerable); // false

Classes

Accessor properties

  • ES5 equivalent
let Person = (function() {
    "use strict";

    const Person = function(name) {
        if (typeof new.target === "undefined") {
            throw new Error("Constructor must be called with new.");
        }
        this.name = name;
    }

    Object.defineProperty(CustomHTMLElement.prototype, "name", {
        enumerable: false,
        configurable: true,
        get: function() {
            return this.name;
        },
        set: function(value) {
            this.name= value;
        }
    });

    return Person;
}());

Classes

Static methods

  • ES5 functions
function PersonType(name) {
    this.name = name;
}

// static method
PersonType.create = function(name) {
    return new PersonType(name);
};

// instance method
PersonType.prototype.sayName = function() {
    console.log(this.name);
};

var person = PersonType.create("Andrei");

Classes

Static method

class PersonClass {
    
    constructor(name) { // same as PersonType constructor
        this.name = name;
    }
    
    sayName() { // same as PersonType.prototype.sayName
        console.log(this.name);
    }

    // equivalent of PersonType.create
    static create(name) {
        return new PersonClass(name);
    }
}

let person = PersonClass.create("Andrei");

Classes

Inheritance - ES5

function Rectangle(length, width) {
    this.length = length;
    this.width = width;
}

Rectangle.prototype.getArea = function() {
    return this.length * this.width;
};
function Square(length) {
    Rectangle.call(this, length, length);
}

Square.prototype = Object.create(Rectangle.prototype, {
    constructor: {
        value:Square,
        enumerable: true,
        writable: true,
        configurable: true
    }
});


var square = new Square(3);

console.log(square.getArea());              // 9
console.log(square instanceof Square);      // true
console.log(square instanceof Rectangle);   // true
var square = new Square(3);

console.log(square.getArea());              // 9
console.log(square instanceof Square);      // true
console.log(square instanceof Rectangle);   // true

Classes

Inheritance - Classes

class Rectangle {
    constructor(length, width) {
        this.length = length;
        this.width = width;
    }

    getArea() {
        return this.length * this.width;
    }
}
class Square extends Rectangle {
    constructor(length) {

        super(length, length);  // same as Rectangle.call(this, length, length)
    }
}
var square = new Square(3);

console.log(square.getArea());              // 9
console.log(square instanceof Square);      // true
console.log(square instanceof Rectangle);   // true
  • Derived classes require you to use super() if you specify a constructor

Classes

Shadowing methods

class Square extends Rectangle {
    constructor(length) {
        super(length, length);
    }

    // override and shadow Rectangle.prototype.getArea()
    getArea() {
        return this.length * this.length;
    }
}
class Square extends Rectangle {
    constructor(length) {
        super(length, length);
    }

    // override, shadow, and call Rectangle.prototype.getArea()
    getArea() {
        return super.getArea();
    }
}
  • You can call the base class version of the method by using the super

Classes

Abstract classes

// abstract base class
class Shape {
    constructor() {
        if (new.target === Shape) {
            throw new Error("This class cannot be instantiated directly.")
        }
    }
}

class Rectangle extends Shape {
    constructor(length, width) {
        super();
        this.length = length;
        this.width = width;
    }
}

var x = new Shape();                // throws error

var y = new Rectangle(3, 4);        // no error
console.log(y instanceof Shape);    // true

SET AND MAP

Set an Map

  • A SET is a list of values that cannot contain duplicates.
    • usually used to check presence of value in collection

 

  • A MAP is a collection of keys that correspond to specific values.
    • each item in a map stores two pieces of data, and values are retrieved by specifying the key to read from.
    • are frequently used as caches, for storing data to be quickly retrieved later

Set an Map

Implementing in ES5

  • Set
let set = Object.create(null);

set.foo = true;

// checking for existence
if (set.foo) {
    ...
}
let map = Object.create(null);

map.foo = "bar";

// retrieving a value
let value = map.foo;

console.log(value);         // "bar"
  • Map

Set an Map

  • Common problems with ES5 implementation
let map = Object.create(null);

map[5] = "foo";

console.log(map["5"]);      // "foo"
let map = Object.create(null),
    key1 = {},
    key2 = {foo: "foo"};

map[key1] = "bar";

console.log(map[key2]);     // "bar"

console.log(key1.toString());   // [object Object]
console.log(key2.toString());   // [object Object]

Set an Map

SETS

  • Creation
let set = new Set();

console.log(set.size); // 0
  • Adding elements
set.add(3);
set.add(5);
set.add("5");
console.log(set.size);    // 3
let set = new Set(),
    key1 = {},
    key2 = {};
set.add(key1);
set.add(key2);
let set = new Set();
set.add(5);
set.add("5");
set.add(5);
console.log(set.size);    // 2
console.log(set.size);    // 2

Set an Map

SETS

  • Initialization from array
let set = new Set([1, 2, 3, 4, 5, 5, 4, 3]);
  • Checking element existance
console.log(set.size);    // 5
let set = new Set([5, "5"]);

set.delete(5);

console.log(set.has(5));    // false
console.log(set.size);      // 1

set.clear();

console.log(set.has("5"));  // false
console.log(set.size);      // 0
  • Removing elements
let set = new Set();
set.add(5);
set.add("5");

console.log(set.has(5));    // true
console.log(set.has(6));    // false

Set an Map

SETS

  • Retention of elements
let set = new Set(),
    key = {};

set.add(key);
console.log(set.size);      // 1

// eliminate original reference
key = null;

console.log(set.size);      // 1
  • Weak Sets - only store weak object references and cannot store primitive values.
    • the only have add(), has() and delete()
    • can hold only objects
    • are not iterables and do not have forEach()
    • do not have a size property.

Set an Map

WEAK SETS

let set = new WeakSet(),
    key = {};

// add the object to the set
set.add(key);

console.log(set.has(key));      // true

// remove the last strong reference to key, also removes from weak set
key = null;

console.log(set.has(key));      // false
  •  if you only need to track object references, then you should use a weak set instead of a regular set.

Set an Map

MAPS

  • Creation
let map = new Map();

console.log(set.size); // 0
  • Adding and retrieving elements
map.set("model", "iPhone");
map.set("number", 7);

console.log(map.get("model"));        // "iPhone"
console.log(map.get("number"));       // 7
let map = new Map(),
    key1 = {},
    key2 = {};

map.set(key1, 5);
map.set(key2, 42);

console.log(map.get(key1));         // 5
console.log(map.get(key2));         // 42

Set an Map

MAPS

  • Checking existance
let map = new Map();
map.set("name", "Andrei");
map.set("age", 29);

console.log(map.size);          // 2

console.log(map.has("name"));   // true
console.log(map.get("name"));   // "Andrei"

console.log(map.has("age"));    // true
console.log(map.get("age"));    // 29
  • Deleting elements
map.delete("name");
console.log(map.has("name"));   // false
console.log(map.get("name"));   // undefined
console.log(map.size);          // 1

map.clear();
console.log(map.has("name"));   // false
console.log(map.get("name"));   // undefined
console.log(map.has("age"));    // false
console.log(map.get("age"));    // undefined
console.log(map.size);          // 0

Set an Map

MAPS

  • Initialization from array
let map = new Map([["name", "Andrei"], ["age", 29]]);

console.log(map.has("name"));   // true
console.log(map.get("name"));   // "Andrei"
console.log(map.has("age"));    // true
console.log(map.get("age"));    // 29
console.log(map.size);          // 2

Set an Map

WEAK MAPS

  • Initialization from array
let map = new Map([["name", "Andrei"], ["age", 29]]);

console.log(map.has("name"));   // true
console.log(map.get("name"));   // "Andrei"
console.log(map.has("age"));    // true
console.log(map.get("age"));    // 29
console.log(map.size);          // 2

DESTRUCTURING

Destructuring

Destructuring - the process of breaking a data structure down into smaller parts

let toDoItem = {
    name: "Walk dog",
    isDone: false
};

// extract data from the object
let name = toDoItem.name;
let isDone = toDoItem.isDone;
  • ES5 - geting data into local variables

Destructuring

Destructuring objects

let toDoItem = {
    name: "Walk dog",
    isDone: false
};

let { name, isDone } = toDoItem;

console.log(name);      // "Walk dog"
console.log(isDone);      // false
  • ES6
  • destructuring requires initialization

// syntax error!
let { name, isDone };

Destructuring

Default values

let toDoItem = {
    name: "Walk dog",
    isDone: false
};

let { name, isDone, tag } = toDoItem;

console.log(name);        // "Walk dog"
console.log(isDone);      // false
console.log(tag);         // undefined
let toDoItem = {
    name: "Walk dog",
    isDone: false
};

let { name, isDone, tag = "Home" } = toDoItem;

console.log(name);        // "Walk dog"
console.log(isDone);      // false
console.log(tag);         // "Home"

Destructuring

Value names

let toDoItem = {
    name: "Walk dog",
    isDone: false
};

let { name: toDoName, isDone: doneState } = toDoItem;

console.log(toDoName);        // "Walk dog"
console.log(doneState);       // false
let toDoItem = {
    name: "Walk dog"
};

let { name: toDoName, isDone: doneState = false } = toDoItem;

console.log(toDoName);        // "Walk dog"
console.log(doneState);       // false
  • with default values

Destructuring

Multilevel destructuring

let toDoItem = {
    details: {
        name: "Walk dog",
        dueDate: "11.03.2017"
    },
    isDone: false
};

// extract toDoItem.details.name
let { details: { name: toDoName } } = toDoItem;

console.log(toDoName)    // "Walk dog"

Destructuring

Array destructuring - works with position in array rather than name

let colors = [ "red", "green", "blue" ];

let [ firstColor, secondColor ] = colors;

console.log(firstColor);        // "red"
console.log(secondColor);       // "green"
let colors = [ "red", [ "green", "lightgreen" ], "blue" ];

// later

let [ firstColor, [ secondColor ] ] = colors;

console.log(firstColor);        // "red"
console.log(secondColor);       // "green"
  • nested destructuring

Destructuring

Array destructuring

let colors = [ "red", "green", "blue" ];

let [ , , thirdColor ] = colors;

console.log(thirdColor);        // "blue"
let a = 1,
    b = 2;

[ a, b ] = [ b, a ];

console.log(a);     // 2
console.log(b);     // 1
  • neat trick - swapping values

Destructuring

Mixed destructuring

let mixed = {
  one: 1,
  two: 2,
  values: [3, 4, 5]
};
let { one: a, two: b, values: [c, , e] } = mixed;

console.log(a); // 1
console.log(b); // 2
console.log(c); // 3
console.log(e); // 5
function con() {
  console.log("TEST");
  return 10;
}

let [x = con()] = [];  // "TEST"
let [x = con()] = [1]; // ""

Lazy defaults

Destructuring

Destructured Function Parameters

const animal = {
  name: 'Dog',
  sound: 'wof'
};

function makeSound(options) {
  options.name = options.name || 'animal';
  console.log(`The ${options.animal} goes ${options.sound}`)
}

makeSound(animal);
const animal = {
  name: 'Dog',
  sound: 'wof'
};

function makeSound({name = 'animal', sound}) {
   console.log(`The ${name} goes ${sound}`)
}

makeSound(animal);

Destructuring

Destructured Function Parameters

function setCookie(name, value,
    {
        secure = false,
        path = "/",
        domain = "example.com",
        expires = new Date(Date.now() + 360000000)
    } = {}
) {

    // ...
}

SPREAD AND REST

Spread and rest

Unnamed Parameters in ES5

function pick(object) {
    let result = Object.create(null);

    // start at the second parameter
    for (let i = 1, len = arguments.length; i < len; i++) {
        result[arguments[i]] = object[arguments[i]];
    }

    return result;
}
let book = {
    title: "Understanding ECMAScript 6",
    author: "Nicholas C. Zakas",
    year: 2015
};

let bookData = pick(book, "author", "year");

console.log(bookData.author);   // "Nicholas C. Zakas"
console.log(bookData.year);     // 2015

Spread and rest

Rest Parameter - is indicated by three dots (...) preceding a named parameter

function pick(object, ...keys) {
    let result = Object.create(null);

    for (let i = 0, len = keys.length; i < len; i++) {
        result[keys[i]] = object[keys[i]];
    }

    return result;
}
  • The rest parameter becomes an Array containing the rest of the parameters passed to the function
  • It allow you to specify that multiple independent arguments should be combined into an array

Spread and rest

Rest Parameter restrictions

function pick(object, ...keys, last) {
    let result = Object.create(null);

    for (let i = 0, len = keys.length; i < len; i++) {
        result[keys[i]] = object[keys[i]];
    }

    return result;
}
  • Can't use rest parameter in setter
  • Can't have a named parameter after rest parameters
let object = {

    set name(...value) {
        // do something
    }
};

Spread and rest

Rest items

let colors = [ "red", "green", "blue" ];

let [ firstColor, ...restColors ] = colors;

console.log(firstColor);        // "red"
console.log(restColors.length); // 2
console.log(restColors[0]);     // "green"
console.log(restColors[1]);     // "blue"

let colors = [ "red", "green", "blue" ];
let [ ...clonedColors ] = colors;

console.log(clonedColors);      //"[red,green,blue]"
  • Cloning an array

Spread and rest

Rest properties (ES2018)

let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };

console.log(x); // 1
console.log(y); // 2
console.log(z); // { a: 3, b: 4 }
  • Rest properties collect the remaining own enumerable property keys that are not already picked off by the destructuring pattern

Spread and rest

Spread operator

function logNumbers(a, b, c) {
  console.log(a)
  console.log(b)
  console.log(c)
}

const array = [1,2,3];

logNumbers(...array); // 1 2 3
  • the spread operator allows you to specify an array that should be split and have its items passed in as separate arguments to a function
  • replacement for apply

var args = [0, 1, 2];
logNumbers.apply(null, args);

Spread and rest

Spread operator - array

const array = [1,2,3];

console.log([...array, 4, 5]);  // [1, 2, 3, 4, 5]
  • Convert a Set into an array
let set = new Set([1, 2, 3, 3, 3, 4, 5]),
    array = [...set];

console.log(array);             // [1,2,3,4,5]
  • You can use the spread operator to work on any iterable objects

var obj = {'key1': 'value1'};
var array = [...obj]; // TypeError: obj is not iterable

Spread and rest

Spread operator - array

Array operations

  • concatenation
const arr1 = [1,2,3];
const arr2 = [2,3,4];

const arr3 = [...arr1, 8, ...arr2];

console.log(arr3); // [1, 2, 3, 8, 2, 3, 4]
  • copy array
const arr1 = [1,2,3];
const arr2 = [...arr1]

console.log(arr1); // [1, 2, 3]
console.log(arr2); // [1, 2, 3]
console.log(arr1 === arr2); // false

Spread and rest

Spread properties (ES2018)

const obj1 = {a: 'a', b: 'b'};
const obj2 = {c: 'c', ...obj1};

console.log(obj2);    // {a: 'a', b: 'b', c: 'c'}
  • in case of multiple same-named properties, the last one wins out
const obj1 = {a: 'a', b: 'b', c: 'c'};
const obj2 = {c: 'd', ...obj1};

console.log(obj2); // {a: 'a', b: 'b', c: 'd'}

MODULES

Modules

Modules - ES6 way to solve the "everything is in one scope" problem

  • Module code automatically runs in strict mode
  • Variables created in the top level of a module aren’t automatically added to the shared global scope.
  • The value of this in the top level of a module is undefined.
  • Modules must export anything that should be available to code outside of the module.
  • Modules may import bindings from other modules.

Modules

Exporting - using the export keyword

export var color = "red";
export let person = { name : "Andrei" };
export const value = 7;
  • Export data
export function sum(num1, num2) {
    return num1 + num1;
}
  • Export functions
export class Rectangle {
    constructor(length, width) {
        this.length = length;
        this.width = width;
    }
}
  • Export functions

Modules

Exporting - using the export keyword

// this function is private to the module
function subtract(num1, num2) {
    return num1 - num2;
}

function multiply(num1, num2) {
    return num1 * num2;
}

export { multiply };
  • Export eferences

Modules

Importing - using the import keyword

import { sum } from "./example.js";

console.log(sum(1, 2));     // 3

sum = 1;        // error
  • Importing a single binding
import * as example from "./example.js";

console.log(example.sum(1, example.number));          // 8
console.log(example.multiply(1, 2));    // 2
  • Importing multiple bindings
import { sum, multiply, number } from "./example.js";

console.log(sum(1, number));   // 8
console.log(multiply(1, 2));        // 2
  • Importing all of the module

Modules

Importing - module gets executed only once

import { sum } from "./example.js";
import { multiply } from "./example.js";
import { number } from "./example.js";
  • import/export keywords must be used outside statements
if (flag) {
    export flag;    // syntax error
}

function tryImport() {
    import flag from "./example.js";    // syntax error
}

Modules

Modifying import bindings

// example.js

export var name = "Andrei";
export function setName(newName) {
    name = newName;
}
import { name, setName } from "./example.js";

console.log(name);       // "Andrei"
setName("Alex");
console.log(name);       // "Alex"

name = "Nicholas";       // error

Modules

Renaming Exports and Imports

function sum(num1, num2) {
    return num1 + num2;
}

export { sum as add };
import { add as sum } from "./example.js";

console.log(typeof add);            // "undefined"
console.log(sum(1, 2));             // 3

Modules

Default values

export default function(num1, num2) {
    return num1 + num2;
}
function sum(num1, num2) {
    return num1 + num2;
}

export { sum as default };
  • Exporting
import sum from "./example.js";

console.log(sum(1, 2));     // 3
  • Importing
export let color = "red";

export default function(num1, num2) {
    return num1 + num2;
}
import sum, { color } from "./example.js";

console.log(sum(1, 2));     // 3
console.log(color);         // "red"

Modules

Importing Without Bindings

// example.js

Array.prototype.pushAll = function(items) {

    // items must be an array
    if (!Array.isArray(items)) {
        throw new TypeError("Argument must be an array.");
    }

    // use built-in push() and spread operator
    return this.push(...items);
};
import "./example.js";

let colors = ["red", "green", "blue"];
let items = [];

items.pushAll(colors);

Modules

Using Modules in Web Browsers

<!-- load a module JavaScript file -->
<script type="module" src="module.js"></script>

<!-- include a module inline -->
<script type="module">

import { sum } from "./example.js";

let result = sum(1, 2);

</script>

ITERATORS

Iterators and generators

The for loop

var colors = ["red", "green", "blue"];

for (var i = 0, len = colors.length; i < len; i++) {
    console.log(colors[i]);
}
  • Tend to get complicated when complex logic and nesting is required

Iterators

function createIterator(items) {
    var i = 0;

    return {
        next: function() {

            var done = (i >= items.length);
            var value = !done ? items[i++] : undefined;

            return {
                done: done,
                value: value
            };

        }
    };
}

var iterator = createIterator([1, 2, 3]);

console.log(iterator.next());           // "{ value: 1, done: false }"
console.log(iterator.next());           // "{ value: 2, done: false }"
console.log(iterator.next());           // "{ value: 3, done: false }"
console.log(iterator.next());           // "{ value: undefined, done: true }"
// for all further calls
console.log(iterator.next());           // "{ value: undefined, done: true }"

Iterators and generators

Generators - functions that return an iterator

// generator
function *createIterator() {
    yield 1;
    yield 2;
    yield 3;
}

// generators are called like regular functions but return an iterator
let iterator = createIterator();

console.log(iterator.next().value);     // 1
console.log(iterator.next().value);     // 2
console.log(iterator.next().value);     // 3

// for all further calls
console.log(iterator.next().value);     // undefined
  • The * before the function name makes the function a generator.
  • The yield keyword specifies values the resulting iterator should return when next() is called

Iterators and generators

Generator Function Expressions

let createIterator = function *(items) {
    for (let i = 0; i < items.length; i++) {
        yield items[i];
    }
};

let iterator = createIterator([1, 2, 3]);
  • Creating an arrow function that is also a generator is not possible.
var o = {

    createIterator: function *(items) {
        for (let i = 0; i < items.length; i++) {
            yield items[i];
        }
    }
};

let iterator = o.createIterator([1, 2, 3]);

Generator Object Methods

Iterators and generators

The for-of loop

let values = [1, 2, 3];

for (let num of values) {
    console.log(num);
}

//   1
//   2
//   3
  • A for-of loop calls next() on an iterable
  • This for-of loop first calls the Symbol.iterator method on the values array to retrieve an iterator.
  • The for-of statement will throw an error when used on, a non-iterable object, null, or undefined.
let divs = document.getElementsByTagName("div"); // NodeList Iterator

for (let div of divs) {
    console.log(div.id);
}

Iterators and generators

Accessing the Default Iterator

let values = [1, 2, 3];
let iterator = values[Symbol.iterator]();

console.log(iterator.next());           // "{ value: 1, done: false }"
console.log(iterator.next());           // "{ value: 2, done: false }"
console.log(iterator.next());           // "{ value: 3, done: false }"
console.log(iterator.next());           // "{ value: undefined, done: true }"
function isIterable(object) {
    return typeof object[Symbol.iterator] === "function";
}

console.log(isIterable([1, 2, 3]));     // true
console.log(isIterable("Hello"));       // true
console.log(isIterable(new Map()));     // true
console.log(isIterable(new Set()));     // true
console.log(isIterable(new WeakMap())); // false
console.log(isIterable(new WeakSet())); // false

Detecting if an object is iterable

Iterators and generators

Creating iterables - make an object iterable by creating a Symbol.iterator property containing a generator

let collection = {
    items: [],
    *[Symbol.iterator]() {
        for (let item of this.items) {
            yield item;
        }
    }

};

collection.items.push(1);
collection.items.push(2);
collection.items.push(3);

for (let x of collection) {
    console.log(x);
}

//  1
//  2
//  3

Iterators and generators

Passing Arguments to Iterators

function *createIterator() {
    let first = yield 1;
    let second = yield first + 2;       // 4 + 2
    yield second + 3;                   // 5 + 3
}

let iterator = createIterator();

console.log(iterator.next());           // "{ value: 1, done: false }"
console.log(iterator.next(4));          // "{ value: 6, done: false }"
console.log(iterator.next(5));          // "{ value: 8, done: false }"
console.log(iterator.next());           // "{ value: undefined, done: true }"

Iterators and generators

Throwing Errors in Iterators

function *createIterator() {
    let first = yield 1;
    let second = yield first + 2;       // yield 4 + 2, then throw
    yield second + 3;                   // never is executed
}

let iterator = createIterator();

console.log(iterator.next());                   // "{ value: 1, done: false }"
console.log(iterator.next(4));                  // "{ value: 6, done: false }"
console.log(iterator.throw(new Error("Boom"))); // error thrown from generator

Iterators and generators

Generator Return Statements

function *createIterator() {
    yield 1;
    return;
    yield 2;
    yield 3;
}

let iterator = createIterator();

console.log(iterator.next());           // "{ value: 1, done: false }"
console.log(iterator.next());           // "{ value: undefined, done: true }"
function *createIterator() {
    yield 1;
    return 42;
}

let iterator = createIterator();

console.log(iterator.next());           // "{ value: 1, done: false }"
console.log(iterator.next());           // "{ value: 42, done: true }"
console.log(iterator.next());           // "{ value: undefined, done: true }"
  • Stop execution
  • Specify last value

Iterators and generators

Asynchronous task running

run(function *() {
    const url = 'https://jsonplaceholder.typicode.com/';
    let response = yield fetch(`${url}posts/1`);

    const post = yield response.json();
    
    const userId = post.userId;
    response = yield fetch(`${url}users/${userId}`);
    
    const user = yield response.json();
    
    return user.name;
})
.then(name => console.log(name));
function run(generator) {
  const iterator = generator();
  function iterate(iteration) {
    if(iteration.done) return iteration.value;
    const promise = iteration.value;
    return promise.then( p => iterate(iterator.next(p)) );
  }
  return iterate(iterator.next());

Iterators and generators

Async await (ES2016)

async function getName() {
  const url = 'https://jsonplaceholder.typicode.com/';
  let response = await fetch(`${url}posts/-1`);

  const post = await response.json();
  const userId = post.userId;

  response = await fetch(`${url}users/${userId}`);

  const user = await response.json();

  return user.name;
}

getName()
  .then(name => console.log(name))
  .catch(error => console.log('error'));

Iterators and generators

OTHER ES6 FEATURES

Other ES6 Features

  • Promises
  • Unicode characters
  • Improved Arrays
  • New Math methods
  • Proxies
  • Reflection API
  • Tail call optimization

JSHacks Sessions - ES6

By Andrei Antal

JSHacks Sessions - ES6

  • 1,389