Andrei Antal

Angular Workshop

ES6 and TypeScript

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 }

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

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

  • Set and Map
  • Symbols
  • Promises
  • Unicode characters
  • Improved Arrays
  • New Math methods
  • Proxies
  • Reflection API
  • Tail call optimization

TYPESCRIPT

TypeScript

  • Superset of ECMAScript 2015 (ES6)
  • Open Source
  • Anders Hejlsberg @ Microsoft, 2012

Static styping

let isDone: boolean = false;
let height: number = 6;
let name: string = "bob";
let list: number[] = [1, 2, 3];
let list: Array<number> = [1, 2, 3];
enum Color {Red, Green, Blue};
let c: Color = Color.Green;
let notSure: any = 4;
notSure = "maybe a string instead";
notSure = false; // okay, definitely a boolean

Classes ++

class Person {
    nickName?: string;
    private name: string;
    public title: string;
    protected furColor: Color;

    public static maximumAge: number = 85;
    
    constructor(name: string) {
        ...
    }
}

const p = new Person("Andrei");

Interfaces

interface ILog {
    LogMessage(message: string) : void;
}

class ConsoleLog implements ILog {
    public LogMessage(message: string) : void {
        console.log(message);
    }
}

class Calculator {
    private log: ILog;

    constructor(log: ILog) {
        this.log = log;
    }

    add(x: number, y: number) : number {
        this.log.LogMessage(`Adding {x} and {y}.`);
        return x+y;
    }
}

Generics

class GenericQueue<T> {
    enqueue(value: T) : void {/*enqueue value */}
    dequeue() : T {/*dequeue value*/}
}

let q2 = new GenericQueue<string>();
q2.enqueue("Hello");
q2.enqueue(17); // compilation error

let q3 = new GenericQueue<Animal>();
q3.enqueue(new Animal("Max"));
q3.enqueue("Hello"); // compilation error

Decorators

function ClassDecoratorParams(param1: number, param2: string) {
    return function(
        target: Function // The class the decorator is declared on
        ) {
        console.log("ClassDecoratorParams(" + param1 + ", '" + 
            param2 + "') called on: ", target);
    }
}

@ClassDecoratorParams(1, "a")
@ClassDecoratorParams(2, "b")
class ClassDecoratorParamsExample {
}
ClassDecoratorParams(2, 'b') called on:  function ClassDecoratorParamsExample() {
 }
ClassDecoratorParams(1, 'a') called on:  function ClassDecoratorParamsExample() {
 }
  • Functions that are invoked with a prefixed @ symbol, and immediately followed by a class, parameter, method or property.

TYPE SYSTEM

TYPE SYSTEM

Values end up in resulting JavaScript, types don't

let animal;         // declares only a value
interface Mamal {}  // declares a type
class Dog {}        // declares a value and a type

TYPE SYSTEM

 

Structural typing

interface Person {
  age: number;
}

class Andrei implements Person {
  constructor(public age: number) {}
}

let pers1: Person = new Andrei(32);
let pers2: Andrei = {age: 32};

All types are expressible as interfaces

interface Node {
  (x: string): string;       // callable

  new (label: string): Node; // constructor

  children: Node[];          // property

  numChildren(): number;     // method
}

INTERFACES

Index signatures

interface Data {
  [key: string]: boolean;
}

const d: Data = {};
d['key1'] = true;
console.log(d['key1']); // true

INTERFACES

UNION/INTERSECTION

 

Union: the union of the posible values

type primitive = string | number | boolean;

Intersection: the intersection of possible values

type MyDiv = HTMLDivElement & {customDivAttr: string};

UNION/INTERSECTION

 

Narrowing: avioding casts

type el = HTMLAnchorElement | HTMLImageElement;

const location = (d: el) => {
  if(d instanceof HTMLAnchorElement) {
    return d.href;
  }

  return d.src;
}

UNION/INTERSECTION

 

Optional arguments

function one(x: string | undefined) { ... }
one();
// Error: requires one argument

function two(x?: string) { ... }
two();
// It's ok

ENUMS

 

Enums assign a numbers/strings to each symbol

enum Card {
  CLUBS = 1, // Subsequent values are incrm. from 1
  DIAMONDS,
  HEARTS,
  SPADES
}

enum Colors {
  RED = '#FF0000',
  GREEN = '#00FF00',
  BLUE = '#0000FF'
}

let myCard = Card.HEARTS; // 3
let red = Colors.RED; // #FF0000

GENERICS

 
class GenericQueue<T> {
    enqueue(value: T) : void {/*enqueue value */}
    dequeue() : T {/*dequeue value*/}
}

let q2 = new GenericQueue<string>();
q2.enqueue("Hello");
q2.enqueue(17); // compilation error

let q3 = new GenericQueue<Animal>();
q3.enqueue(new Animal("Max"));
q3.enqueue("Hello"); // compilation error

Typescript and ES6

By Andrei Antal

Typescript and ES6

  • 983