Understanding ES6 Learning Note

Chapter1 Block Bindings

1.1 Var Declarations and Hoisting

Hoisting for var

  • If in function: as if they are at the top of the function
  • If outside of function: at the top of global scope
  • Not at the top of block
1
2
3
4
5
6
7
8
function alertValue(condition) {
if (condition) {
var value = "blue";
} else {
alert(value); // undefined instead of error
}
alert(value) // undefined instead of error
}

1.2 Block-Level Declarations

  • Block scope is created by:
    • Inside a function
    • Inside of a block (indicated by { and }
  • let (like other language)
    • In same scope
      • no redeclaration
      • can revalue
    • In different scope
      • cover
  • const

    • similar as let
    • except: cannot re-reference, while update object is fine

      1
      2
      3
      const person = { name: "Nicholas"};
      person.name = "Greg"; // works
      person = { name: "Nicholas" }; // error
  • The Temporal Dead Zone

    • var: hoist
    • let/const (in same scope)

      1. Places the declaration in the TDZ
      2. Any attempt to access a variable in the TDZ results in a runtime error
      3. That variable is only removed from the TDZ, and therefore safe to use, once execution flows to the variable declaration
      4. This even affect typeof

        1
        2
        3
        4
        5
        6
        console.log(typeof value);      // "undefined"

        if (condition) {
        console.log(typeof value); // reference error
        let value = "blue";
        }

1.3 Block Binding in Loops

  • declaration in loop

    1
    2
    3
    4
    5
    for (let i = 0; i < 10; i++) {}
    console.log(i); // error

    for (var j = 0; j < 10; j++) {}
    console.log(j) // 10
  • const in for-in and for-of

    1
    2
    3
    for (const i = 0; i < 10; i++) {}       // error

    for (const i in obj) {} // fine
  • function in loop

    1
    2
    3
    4
    5
    6
    7
    var funcs = [];
    for (var i = 0; i < 10; i++) {
    funcs.push(function() { console.log(i); });
    }
    funcs.forEach(function(func) {
    func(); // outputs the number "10" ten times
    });
    1
    2
    3
    4
    5
    6
    7
    var funcs = [];
    for (let i = 0; i < 10; i++) {
    funcs.push(function() { console.log(i); });
    }
    funcs.forEach(function(func) {
    func(); // expect output
    });

1.4 Global Block Bindings

  • var overwrites window attribute
  • let/const declare a new one in scope

Question: in global, what’s the difference between

  • var a = 123 and
  • b = 123

1.5 Emerging Best Practices for Block Bindings

  • default using const if we can
  • then use let if it might change

Chapter2 Strings and Regular Expressions

2.1 Better Unicode Support

Skip

2.2 Other String Changes

  • Identifying Substrings
    • includes()
    • startsWith()
    • endsWith()
    • repeat()

2.3 Other Regular Expression Changes

  • y Flag: sticky property
    • it tells the search to start matching characters in a string at the position specified by the regular expression’s lastIndex property.
    • lastIndex: pattern.lastIndex
    • sticky: pattern.sticky
  • Duplicating RE: let re = new RegExp(re1, "g");
  • flags property

    1
    2
    3
    let re = /ab/g;
    console.log(re.source);
    console.log(re.flags);

2.4 Template Literals

  • Multiline strings

    1
    2
    3
    4
    let html = `
    <div>
    <h1>Title</h1>
    </div>`.trim()
  • Basic string formatting

    • Substitution

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

      console.log(message);
    • Tagged Templates: advanced substitution

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      function passthru(literals, ...substitutions) {
      let result = "";

      // run the loop only for the substitution count
      for (let i = 0; i < substitutions.length; i++) {
      result += literals[i];
      result += substitutions[i];
      }

      // add the last literal
      result += literals[literals.length - 1];

      return result;
      }

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

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

    • String.raw

      1
      2
      3
      4
      5
      6
      let message1 = `Multiline\nstring`,
      message2 = String.raw`Multiline\nstring`;

      console.log(message1); // "Multiline
      // string"
      console.log(message2); // "Multiline\nstring"

Chapter3 Functions

3.1 Functions with Default Parameter Values

  • example: function makeRequest(url, timeout = 2000, callback){}
  • The arguments object will not reflect changes to the named parameters.
  • Default Parameter Expressions
    • function add(first, second = getValue()) { return first + second;}
    • function add(first, second = first)
    • Default Parameter Value Temporal Dead Zone
      1. When executing add(), first, second are added into TDZ
      2. Initialize first then second one by one
      3. if add(first=second, second) will raise exception

3.2 Working with Unnamed Parameters

  • function pick(object, ...keys){}

3.3 Increased Capabilities of the Function Constructor

  • let add = new Function('first', 'second', 'return first+second')

3.4 The Spread Operator

  • example

    1
    2
    let values = [1, 2, 3];
    console.log(Math.max(...values, 0));

3.5 ECMAScript 6’s name Property

  • function’s property

    1
    2
    function doSomething(){}
    console.log(doSomething.name);

3.6 Clarifying the Dual Purpose of Functions

  • Two different function

    • call function with new
      • [[Construct]] method is excuted with this set
    • call function without new

      • [[Call]] method is executed

        1
        2
        3
        4
        5
        6
        7
        8
        9
        function Person(name) {
        this.name = name;
        }

        var person = new Person("Nicholas");
        var notAPerson = Person("Nicholas");

        console.log(person); // "[Object object]"
        console.log(notAPerson); // "undefined"
  • The new.target MetaProperty: solve above issue

    1
    2
    3
    4
    5
    6
    7
    function Person(name) {
    if (typeof new.target !== "undefined") {
    this.name = name;
    } else {
    throw new Error("use new");
    }
    }

3.7 Block-Level Functions

  • Difference between strict mode or not

    1
    2
    3
    4
    5
    6
    if (true) {
    console.log(typeof doSomething); // function
    let doSomething = function () {}
    }
    console.log(typeof doSomething); // throw error in strict mode
    console.log(typeof doSomething); // function in nonstrict mode

3.8 Arrow Functions

  • No this, super, arguments, and new.target bindings
  • Cannot be called with new
  • No prototype
  • Can’t change this
  • No arguments object
  • No duplicate named parameters
  • example

    1
    2
    3
    4
    5
    6
    let person = function(name) {
    return {
    getName: function() {return name;}
    };
    }("Nicholas");
    console.log(person.getName()); // "Nicholas"

3.9 Tail Call Optimization

  • With this optimization, instead of creating a new stack frame for a tail call, the current stack frame is cleared and reused
  • pre-conditions
    • The tail call does not require access to variables in the current stack frame (meaning the function is not a closure)
    • The function making the tail call has no further work to do after the tail call returns
    • The result of the tail call is returned as the function value
  • example

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    // before
    function factorial(n) {
    if (n <= 1) {
    return 1;
    } else {
    // not optimized - must multiply after returning
    return n * factorial(n - 1);
    }
    }

    // after
    function factorial(n, p = 1) {
    if (n <= 1) {
    return 1 * p;
    } else {
    let result = n * p;
    // optimized
    return factorial(n - 1, result);
    }
    }

Chapter4 Expanded Object Functionality

4.1 Object Categories

Name Explanation
Ordinary objects Have all the default internal behaviors for objects in JavaScript.
Exotic objects Have internal behavior that differs from the default in some way.
Standard objects Are those defined by ECMAScript 6, such as Array, Date, and so on. Standard objects may be ordinary or exotic.
Built-in objects Are present in a JavaScript execution environment when a script begins to execute. All standard objects are built-in objects.

4.2 Object Literal Syntax Extensions

  • Property Initializer Shorthand

    1
    2
    3
    4
    5
    6
    function createPerson(name, age) { 
    return {name: name, age: age};
    }
    function createPerson(name, age) {
    return {name, age};
    }
  • Concise Methods

    1
    2
    3
    var person = {
    sayName() { console.log(this.name);}
    };
  • Computed Property Names

    1
    2
    3
    4
    5
    6
    // syntax error in ES5
    var person = {
    "first name": "Nicholas"
    };
    person["first name"] = "John";
    person["first" + suffix] = "Nicholas";

4.3 New Methods

Name Description
Object.is() Similar as ===, but better for (+0, -0) and (NaN, NaN)
Object.assign() one object receives properties and methods from another object

4.4 Duplicate Object Literal Properties

  • No error even in strict mode

    1
    2
    3
    4
    5
    6
    7
    8
    "use strict";

    var person = {
    name: "Nicholas",
    name: "Greg" // no error in ES6 strict mode
    };

    console.log(person.name); // "Greg"

4.5 Own Property Enumeration Order

  • This affects how properties are returned using
    • Object.getOwnPropertyNames()
    • Reflect.ownKeys (Chapter 12)
    • Object.assign()
  • Rule
    1. All numeric keys in ascending order
    2. All string keys in the order in which they were added to the object
    3. All symbol keys (Chapter 6) in the order in which they were added to the object

4.6 More Powerful Prototypes

  • Changing an Object’s Prototype

    • No standard way to change an object’s prototype after instantiation in ES5

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      let person = {
      getGreeting() {
      return "Hello";
      }
      };

      let dog = {
      getGreeting() {
      return "Woof";
      }
      };

      // prototype is person
      let friend = Object.create(person);
      console.log(friend.getGreeting()); // "Hello"
      console.log(Object.getPrototypeOf(friend) === person); // true

      // set prototype to dog
      Object.setPrototypeOf(friend, dog);
      console.log(friend.getGreeting()); // "Woof"
      console.log(Object.getPrototypeOf(friend) === dog); // true
  • Easy Prototype Access with Super References

    • Introduce super keyword

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      let friend = {
      getGreeting() {
      return Object.getPrototypeOf(this).getGreeting.call(this) + ", hi!";
      }
      };
      // the same as
      let friend = {
      getGreeting() {
      return super.getGreeting() + ", hi!";
      }
      };
  • Attempting to use super outside of concise methods results in a syntax error

    1
    2
    3
    4
    5
    6
    let friend = {
    getGreeting: function() {
    // syntax error
    return super.getGreeting() + ", hi!";
    }
    };

4.7 A Formal Method Definition

  • ECMAScript 6 formally defines a method as a function
  • This function has an internal [[HomeObject]] property containing the object to which the method belongs
  • The [[HomeObject]] for getGreeting() method is person
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    let person = {
    // method
    getGreeting() {
    return "Hello";
    }
    };

    // not a method: because not in an object
    function shareGreeting() {
    return "Hi!";
    }
  • Advanced knowledge: Any reference to super uses the [[HomeObject]] to determine what to do

    1. Call Object.getPrototypeOf() on the [[HomeObject]] to retrieve a reference to the prototype.
      • [[HomeObject]] of friend.getGreeting() is friend
    2. the prototype is searched for a function with the same name.
      • the prototype of friend is person
    3. the this binding is set and the method is called.

      • bind this to person
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      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!"

Chapter5 Destructuring for Easier Data Access

5.1 Why is Destructuring Useful?

The ECMAScript 6 implementation actually use syntax for object and array literals.

5.2 Object Destructuring

  • Destructuring Assignment

    • All destructuring should be initialized when declaring
      1
      2
      3
      let { type, name } = node;
      ({ type, name } = node); //change the values of variables
      outputInfo({ type, name } = node);
  • Default Values

    1
    let { type, name, value = true } = node;
  • Assigning to Different Local Variable Names

    1
    2
    3
    let { type: localType, name: localName = "bar" } = node;    // default value

    console.log(localName); // "bar"
  • Nested Object Destructuring

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    let node = {
    type: "Identifier",
    name: "foo",
    loc: {
    start: {
    line: 1,
    column: 1
    },
    end: {
    line: 1,
    column: 4
    }
    }
    };

    // extract node.loc.start
    let { loc: { start: localStart }} = node;

    console.log(localStart.line); // 1
    console.log(localStart.column); // 1

5.3 Array Destructuring

  • Destructuring Assignment

    1
    2
    3
    4
    5
    let colors = [ "red", "green", "blue" ];

    let [ , , thirdColor ] = colors;
    [firstColor, secondColor, thirdColor] = colors; // no bracket compared with obj destruct
    [ a, b ] = [ b, a ]; // swap variable
  • Default Values

    1
    let [ firstColor, secondColor = "green" ] = colors;
  • Nested Destructuring

    1
    2
    3
    4
    let colors = [ "red", [ "green", "lightgreen" ], "blue" ];

    // later
    let [ firstColor, [ secondColor ] ] = colors;
  • Rest Items

    1
    2
    3
    let colors = [ "red", "green", "blue" ];

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

5.4 Mixed Destructuring

  • Combination of Object Destructuring and Array Destructuring
  • Use case: fetch info from JSON

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    let node = {
    type: "Identifier",
    name: "foo",
    loc: {
    start: {
    line: 1,
    column: 1
    },
    end: {
    line: 1,
    column: 4
    }
    },
    range: [0, 3]
    };

    let {
    loc: { start },
    range: [ startIndex ]
    } = node;

    console.log(start.line); // 1
    console.log(start.column); // 1
    console.log(startIndex); // 0

5.5 Destructured Parameters

  • It supports everything in this chapter, including:

    • default values
    • mix object and array patterns
    • use variable names that differ from the properties

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      function setCookie(name, value, { secure, path, domain, expires }) {
      // code to set the cookie
      }

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

      setCookie("type", "js", {
      secure: true,
      expires: 60000
      });

Chapter6 Symbols and Symbol Properties

The private names proposal eventually evolved into ECMAScript 6 symbols

6.1 Creating Symbols

  • Creating and Typeof

    1
    2
    3
    let firstName = Symbol("first name");
    console.log(firstName); // print firstName.toString(), which is [[Description]]
    console.log(typeof firstName); // "symbol"

6.2 Using Symbols

  • computed object literal property names

    1
    2
    3
    let person = {
    [firstName]: "Nicholas"
    };
  • Object.defineProperty()

    1
    Object.defineProperty(person, firstName, { writable: false });

6.3 Sharing Symbols

  • create a shared symbol

    1
    2
    3
    4
    let uid = Symbol.for("uid");
    let uid2 = Symbol.for("uid");

    console.log(uid === uid2); // true
  • access global shared symbol key

    1
    console.log(Symbol.keyFor(uid2));   // "uid"

6.4 Symbol Coercion

  • Don’t do it

6.5 Retrieving Symbol Properties

  • Object.keys() and Object.getOwnPropertyNames() don’t support Symbol property
  • Instead Object.getOwnProperty-Symbols()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    let uid = Symbol.for("uid");
    let object = {
    [uid]: "12345"
    };

    let symbols = Object.getOwnPropertySymbols(object);

    console.log(symbols.length); // 1
    console.log(symbols[0]); // "Symbol(uid)"
    console.log(object[symbols[0]]); // "12345"

6.6 Exposing Internal Operations with Well-Known Symbols

  • An example

    1
    2
    3
    4
    5
    6
    let collection = {
    0: "Hello",
    1: "world",
    length: 2,
    [Symbol.isConcatSpreadable]: true
    };
  • Full list

Symbol description
Symbol.hasInstance A method used by instanceof to determine an object’s inheritance.
Symbol.isConcatSpreadable A Boolean value indicating that Array.prototype.concat() should flatten the collection’s elements if the collection is passed as a parameter to Array.prototype.concat().
Symbol.iterator A method that returns an iterator. (Chapter 8)
Symbol.match Used by String.prototype.match() to compare strings.
Symbol.replace Uused by String.prototype.replace() to replace substrings.
Symbol.search Used by String.prototype.search() to locate substrings.
Symbol.species The constructor for making derived objects. (Chapter 9)
Symbol.split Used by String.prototype.split() to split up strings.
Symbol.toPrimitive A method that returns a primitive value representation of an object.
Symbol.toStringTag Used by Object.prototype.toString() to create an object description.
Symbol.unscopables An object whose properties are the names of object properties that should not be included in a with statement.

Chapter7 Sets and Maps

7.1 Sets and Maps in ECMAScript 5

  • Example
    1
    2
    3
    4
    5
    6
    let set = Object.create(null);

    set.foo = true; // set
    set.foo = bar; // map
    if (set.foo) {
    }

7.2 Problems with Workarounds

  • a[0] and a[“0”]: different type but as same key
  • a[{}] and a[{}]: different obj but as same key

7.3 Sets in ECMAScript 6

  • Init: let a = new Map();
  • Methods:
    • add()
    • size
    • has()
    • delete()
    • clear()
  • forEach(function(next, key, ownerSet), thisArg)

    • next always equal to key
    • ownerSet: set itself
    • thisArg: defined this in function
    • Use an arrow function to get the same effect without passing thisArg
    • 1
      2
      3
      process(dataSet) {
      dataSet.forEach((value) => this.output(value));
      }
      1
      2
      3
      4
      5
      process(dataSet) {
      dataSet.forEach(function(value) {
      this.output(value);
      }, this);
      }
  • Set vs Array

convert implement
Set -> Array [...set]
Array -> Set new Set(arr);
  • Weak Set

    • Issue with Set: garbage collection

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      let set = new Set(),
      key = {};

      set.add(key);

      key = null;

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

      // get the original reference back
      key = [...set][0];
    • Init weak set: new WeakSet()

    • Difference:
      1. add() can only be object
      2. Not iterable: cannot use for-of (link 8.4)
      3. Do not expose iterator: e.g. no keys(), values()
      4. No forEach()
      5. No size attribute

7.4 Maps in ECMAScript 6

  • Init:
    • let a = new Map();
    • let a = new Map([["kk", "vv"], "kkk", "vvv"]]);
  • Methods:
    • set()
    • get()
    • size
    • has()
    • delete()
    • clear()
  • forEach(): similar as Set, but callback function(key, value, ownerMet)
  • Weak Set: similar as Set

    • Only key is weak reference, not value
    • Use case:

      • associated data with DOM elements
        • key is removed authomatically while it is removed from DOM
        • never mind
      • private attribute: amazing !!!

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        let Person = (function() {

        let privateData = new WeakMap();

        function Person(name) {
        privateData.set(this, { name: name });
        }

        Person.prototype.getName = function() {
        return privateData.get(this).name;
        };

        return Person;
        }());

Chapter8 Iterators and Generators

Use case:

  • for-of
  • spread operator: ...
  • Asynchronous

8.1 The Loop Problem

8.2 What are Iterators?

  • Iterators are just objects with a specific interface designed for iteration.
  • All iterator objects have a next() method that returns
    • {value: value, done: bool}

8.3 What Are Generators?

  • Definition
    • indicated by a star character (*) after the function keyword
    • use the new yield keyword
      • yield can only be used in generator or syntax error will show up
    • fetch value by next()
  • Example

    • function keyword

      1
      2
      3
      4
      5
      6
      7
      8
      function *createIterator() {
      yield 1;
      yield 2;
      yield 3;
      }
      let iterator = createIterator();

      console.log(iterator.next().value); // 1
    • Generator Function Expression

      1
      2
      3
      let createIterator = function *(items) {
      yield items[i];
      };
    • Generator Object Methods

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      var o = {
      createIterator: function *(items) {
      yield items[i];
      }
      };

      // method shorthand
      var o = {
      *createIterator(items) {
      yield items[i];
      }
      };

8.4 Iterables and for-of

  • Iterables: An iterable is an object with a Symbol.iterator property.

  • for-of

    • for-of loop first calls the Symbol.iterator method on array/set/map to retrieve an iterator.
    • for-of loop calls next() on an iterable each time the loop executes
    • stores the value from the result object in a variable

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

      console.log(iterator.next());
  • Creating Iterables
    1
    2
    3
    4
    5
    6
    7
    8
    9
    let collection = {
    items: [],
    *[Symbol.iterator]() {
    for (let item of this.items) {
    yield item;
    }
    }

    };

8.5 Built-in Iterators

  • entries(): Map default
  • keys()
  • values(): Set, Array default
  • Example

    1
    2
    3
    for (let entry of map.entries()){}
    // the same as
    for (let entry of map){} // Destructing: for (let [k, v] of map) {}
  • String, NodeList are also iterable

8.6 The Spread Operator and Non-Array Iterables

  • 1
    2
    3
    let smallNumbers = [1, 2, 3],
    bigNumbers = [100, 101, 102],
    allNumbers = [0, ...smallNumbers, ...bigNumbers];

8.7 Advanced Iterator Functionality

  • Passing Arguments to Iterators

    • The first call to next() is lost.
    • arguments passed to next() become the values returned by yield

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      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 }"
  • Throwing Errors in Iterators

    • console.log(iterator.throw(new Error("Boom")));
    • can have try-catch in generator
  • Generator Return Statements

    • indicates that all processing is done, so the done property is set to true
    • the value, if provided, becomes the value field
  • Delegating Generators

    1
    2
    3
    4
    5
    function *createCombinedIterator() {
    yield *createNumberIterator();
    yield *createColorIterator();
    yield 123;
    }

8.8 Asynchronous Task Running

  • Use case:

    • run asynchronous task by order
    • callback for asynchronous task
  • Principle

    • Store a series of asyn function in generator
    • Only after yeilding the result of an asyn function, we do next one
  • Final

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    let fs = require("fs");

    function readFile(filename) {
    return function(callback) { // the callback for result.value
    fs.readFile(filename, callback);
    };
    }

    function run(taskDef) {

    // create the iterator, make available elsewhere
    let task = taskDef();

    // start the task
    let result = task.next();

    function step() {

    if (!result.done) {
    if (typeof result.value === "function") { // check if need to run callback
    result.value(function(err, data) {
    if (err) {
    result = task.throw(err);
    return;
    }

    result = task.next(data);
    step();
    });
    } else {
    result = task.next(result.value);
    step();
    }

    }
    }

    // start the process
    step();

    }

    run(function*() {
    let contents = yield readFile("config.json");
    doSomethingWith(contents);
    console.log("Done");
    });

Chapter9 Introducing JavaScript Classes

9.1 Class-Like Structures in ECMAScript 5

  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    function PersonType(name) {
    this.name = name;
    }

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

    let person = new PersonType("Nicholas");
    person.sayName(); // outputs "Nicholas"

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

9.2 Class Declarations

9.2.1 Example

  • example

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    class PersonClass {
    constructor(name) {
    this.name = name;
    }
    sayName() {
    console.log(this.name);
    }
    }

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

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

    console.log(typeof PersonClass); // "function"
    console.log(typeof PersonClass.prototype.sayName); // "function"
  • The PersonClass declaration actually creates a function that has the behavior of the constructor method, which is why typeof PersonClass gives “function” as the result

  • The sayName() method also becomes a method on PersonClass.prototype

9.2.2 important difference with custom types

  1. Class declarations are not hoisted. Act like let declarations and so exist in the temporal dead zone until execution reaches the declaration.
  2. All code inside of class declarations runs in strict mode automatically.
  3. All methods are non-enumerable.
  4. All methods lack an internal [[Construct]] method and will throw an error if you try to call them with new.
  5. Calling the class constructor without new throws an error.
  6. Attempting to overwrite the class name within a class method throws an error.
  • Equivalent of PersonClass, this is awesome

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    let PersonType2 = (function() {
    "use strict";
    const PersonType2 = function(name) {
    // make sure the function was called with new
    if (typeof new.target === "undefined") {
    throw new Error("Constructor must be called with new.");
    }
    this.name = name;
    }
    Object.defineProperty(PersonType2.prototype, "sayName", {
    value: function() {
    // make sure the method wasn't called with new
    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 PersonType2;
    }());

9.3 Class Expressions

  • 1
    2
    3
    4
    let PersonClass = class {
    //...
    }
    let p = new PersonClass("Nicholas");

9.4 Classes as First-Class Citizens

  • first-class citizen means:

    • it can be used as a value
    • it can be passed into a function
    • returned from a function
    • assigned to a variable

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      function createObject(classDef) {
      return new classDef();
      }

      let obj = createObject(class {

      sayHi() {
      console.log("Hi!");
      }
      });

      obj.sayHi();
  • creating singletons

    1
    let person = new class {...}("Nicholas");

9.5 Accessor Properties

  • getter and setter
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    class CustomHTMLElement {
    constructor(element) {
    this.element = element;
    }
    get html() {
    return this.element.innerHTML;
    }
    set html(value) {
    this.element.innerHTML = value;
    }
    }

    var descriptor = Object.getOwnPropertyDescriptor(CustomHTMLElement.prototype, "ht\
    ml");
    console.log("get" in descriptor); // true
    console.log("set" in descriptor); // true
    console.log(descriptor.enumerable); // false

9.6 Computed Member Names

  • Nothing special
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    let propertyName = "html";
    class CustomHTMLElement {
    constructor(element) {
    this.element = element;
    }
    get [propertyName]() {
    return this.element.innerHTML;
    }
    set [propertyName](value) {
    this.element.innerHTML = value;
    }
    }

9.7 Generator Methods

  • Naive example

    1
    2
    3
    4
    5
    6
    7
    8
    9
    class MyClass {
    *createIterator() {
    yield 1;
    yield 2;
    yield 3;
    }
    }
    let instance = new MyClass();
    let iterator = instance.createIterator();
  • defining a default iterator for your class

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    class Collection {
    constructor() {
    this.items = [];
    }
    *[Symbol.iterator]() {
    yield *this.items.values();
    }
    }
    var collection = new Collection();
    collection.items.push(1);
    collection.items.push(2);
    collection.items.push(3);

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

9.8 Static Members

  • 1
    2
    3
    4
    5
    class PersonClass {
    static create(name) {
    return new PersonClass(name);
    }
    }

9.9 Inheritance with Derived Classes

9.9.1 Inheritance with Derived Classes

  • If specifying a constructor: super() is mandatory
  • If no constructor, super() is automatically called with all arguments upon creating a new instance of the class.
  • Can only use super() in a derived class.
  • Must call super() before accessing this in the constructor
  • The only way to avoid calling super() is to return an object from the class constructor.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    class Square extends Rectangle {
    // no constructor
    }
    // Is equivalent to
    class Square extends Rectangle {
    constructor(...args) {
    super(...args);
    }
    }

9.9.2 Shadowing Class Method

  • Just like other languages

    1
    2
    3
    4
    5
    6
    7
    8
    9
    class Square extends Rectangle {
    constructor(length) {
    super(length, length);
    }
    // override and shadow Rectangle.prototype.getArea()
    getArea() {
    return this.length * this.length;
    }
    }

9.9.3 Inherited Static Members

  • Just like other languages

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    class Rectangle {
    constructor(length, width) {
    this.length = length;
    this.width = width;
    }
    getArea() {
    return this.length * this.width;
    }
    static create(length, width) {
    return new Rectangle(length, width);
    }
    }

    class Square extends Rectangle {
    constructor(length) {
    // same as Rectangle.call(this, length, length)
    super(length, length);
    }
    }

    var rect = Square.create(3, 4);

    console.log(rect instanceof Rectangle); // true
    console.log(rect.getArea()); // 12
    console.log(rect instanceof Square); // false

9.9.4 Derived Classes from Expressions

  • Special
    • You can use extends with any expression as long as the expression resolves to a function with [[Construct]] and a prototype
    • Use case: dynamic extends
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      function Rectangle(length, width) {
      this.length = length;
      this.width = width;
      }

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

      function getBase() {
      return Rectangle;
      }

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

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

9.9.5 Inheriting from Built-ins

  • Just like other languages

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class MyArray extends Array {
    // empty
    }

    var colors = new MyArray();
    colors[0] = "red";
    console.log(colors.length); // 1

    colors.length = 0;
    console.log(colors[0]); // undefined

9.9.6 The Symbol.species Property

  • Why?

    • They think subitems should not be instanceof MyArray, while I feel it’s good

      1
      2
      3
      4
      5
      6
      7
      8
      9
      class MyArray extends Array {
      // empty
      }

      let items = new MyArray(1, 2, 3, 4),
      subitems = items.slice(1, 3);

      console.log(items instanceof MyArray); // true
      console.log(subitems instanceof MyArray); // true
  • The Symbol.species well-known symbol is used to define a static accessor property that returns a function. The following builtin types have Symbol.species defined:

    • Array
    • ArrayBuffer (discussed in Chapter 10)
    • Map
    • Promise
    • RegExp
    • Set
    • Typed Arrays (discussed in Chapter 10)

9.10 Using new.target in Class Constructors

  • determine how the class is being invoked
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    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

Chapter10 Improved Array Capabilities

  • Creating Arrays

    • Array.of()

      1
      2
      3
      Array.of(1, 2);    //[1, 2]
      Array.of(1); //[1]
      Array.of("1"); //["1"]
    • Array.from(iterable, map, this)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      let helper = {
      diff: 1,

      add(value) {
      return value + this.diff;
      }
      };

      function translate() {
      return Array.from(arguments, helper.add, helper);
      }

      let numbers = translate(1, 2, 3);

      console.log(numbers); // 2,3,4
  • New Methods on All Arrays

    • find
      • find(callback, this)
      • findIndex(callback, this)
    • fill(value, startIndex, endIndex)
    • copyWithin(startFillIndex, startCopyIndex, howMany)
  • Typed Arrays
    • Improve calculation speed
    • Skip

Chapter11 Promises and Asynchronous Programming

11.1 Asynchronous Programming Background

  • Background
    • JavaScript engines are built on the concept of a single-threaded event loop
    • Whenever a piece of code is ready to be executed, it is added to the job queue
    • job execution runs from the first job in the queue to the last
  • Old Patterns
    • The Event Model
      • chaining multiple separate asynchronous calls together is more complicated
      • button were clicked before onclick is assigned
    • The Callback Pattern
      • nested callback is dizaster
      • asynchronous operations to run in parallel

11.2 Promise Basics

  • [[PromiseState]]: not exposed
    • Fulfilled: Settled. Completed successfully.
    • Rejected: Settled. Didn’t complete successfully
    • Pending: Unsettled
  • Promise vs thenable

    • all promises are thenable
    • not all thenables are promise
    • all thenable has a then(fulFill, reject)
    • all promise has a then(fulFill, reject) and catch(reject) method

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      let promise = readFile("example.txt");

      promise.then(function(contents) {
      // fulfillment
      console.log(contents);
      }, function(err) {
      // rejection
      console.error(err.message);
      });

      // same
      promise.then(null, reject);
      promise.catch(reject);
    • Each call to then() or catch() creates a new job to be executed when the promise is resolved

    • Without a rejection handler to a promise, all failures will happen silently
  • Creating Unsettled Promises

    • new Promise(excutor)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      let fs = require("fs");

      function readFile(filename) {
      return new Promise(function(resolve, reject) {
      // trigger the asynchronous operation
      fs.readFile(filename, { encoding: "utf8" }, function(err, contents) {
      // check for errors
      if (err) {
      reject(err);
      return;
      }
      // the read succeeded
      resolve(contents);
      });
      });
      }
    • fulfillment and rejection handlers are always added to the end of the job queue after the executor has completed. Behave like setTimeout

      • example

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        let promise = new Promise(function(resolve, reject) {
        console.log("Promise");
        resolve();
        });

        promise.then(function() {
        console.log("Resolved.");
        });

        console.log("Hi!");

        // Promise
        // Hi!
        // Resolved

11.3 Global Promise Rejection Handling

  • Issue: silent failure that occurs when a promise is rejected without a rejection handler
  • Solution: Event + setInterval

    • event
      • unhandlerejection
      • rejectionhandled
    • setInterval
    • example

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      let possiblyUnhandledRejections = new Map();
      window.onunhandledrejection = function(event) {
      possiblyUnhandledRejections.set(event.promise, event.reason);
      };
      window.onrejectionhandled = function(event) {
      possiblyUnhandledRejections.delete(event.promise);
      };

      setInterval(function() {
      possiblyUnhandledRejections.forEach(function(reason, promise) {
      handleRejection(promise, reason);
      });
      possiblyUnhandledRejections.clear();

      }, 60000);

11.4 Chaining Promises

  • Each call to then() or catch() actually creates and returns another promise
  • This second promise is resolved only once the first has been fulfilled or rejected
  • example

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    let p1 = new Promise(function(resolve, reject) {resolve(42);});

    p1.then(function(value) {
    console.log(value);
    }).then(function() {
    console.log("then");
    throw new Error("Boom");
    }).catch(function(error) {
    console.log(error.message);
    });
  • Returning Values in Promise Chains

    • data

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      let p1 = new Promise(function(resolve, reject) {
      reject(42);
      });

      p1.catch(function(value) {
      console.log(value); // "42"
      return value + 1;
      }).then(function(value) {
      console.log(value); // "43"
      });
    • Promise

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      let p1 = new Promise(function(resolve, reject) {
      reject(42);
      });

      p1.catch(function(value) {
      console.log(value); // "42"
      let p2 = new Promise(function(resolve, reject) {
      reject(43);
      });
      return p2
      }).then(function(value) {
      console.log(value); // "43"
      });

11.5 Responding to Multiple Promises

  • Promise.all()
    • The returned promise is fulfilled when every promise in the iterable is fulfilled
    • If anyone is rejected, it is immediately rejected without waiting for others
  • Promise.race()
    • returns as soon as any promise in the array is fulfilled
    • if the first promise to settle is rejected, then the returned promise is rejected

11.6 Inheriting from Promises

  • Since static methods are inherited, resolve(), reject(), race(), all() method are also present on derived promises
  • Because of Symbol.species, resolve(), reject() will tag class as the derived class

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    let p1 = new Promise(function(resolve, reject) {
    resolve(42);
    });

    let p2 = MyPromise.resolve(p1);
    p2.success(function(value) {
    console.log(value); // 42
    });

    console.log(p2 instanceof MyPromise); // true

Chapter12 Proxies and the Reflection API

  • Proxies exposes the inner workings of objects through
  • Proxies can intercept and alter low-level operations of the JavaScript engine.

12.1 The Array Problem

  • User cannot do arr.length = 2

12.2 What are Proxies and Reflection?

Proxy Trap Overrides the Behavior Of Default Behavior
get Reading a property value Reflect.get()
set Writing to a property Reflect.set()
has The in operator Reflect.has()
deleteProperty The delete operator Reflect.deleteProperty()
getPrototypeOf Object.getPrototypeOf() Reflect.getPrototypeOf()
setPrototypeOf Object.setPrototypeOf() Reflect.setPrototypeOf()
isExtensible Object.isExtensible() Reflect.isExtensible()
preventExtensions Object.preventExtensions() Reflect.preventExtensions()
getOwnPropertyDescriptor Object.getOwnPropertyDescriptor() Reflect.getOwnPropertyDescriptor()
defineProperty Object.defineProperty() Reflect.defineProperty
ownKeys Object.keys, Object.getOwnPropertyNames(), Object.getOwnPropertySymbols() Reflect.ownKeys()
apply Calling a function Reflect.apply()
construct Calling a function with new Reflect.construct()
  • example set: only allow numbers for new property

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    let target = {
    name: "target"
    };

    let proxy = new Proxy(target, {
    set(trapTarget, key, value, receiver) {

    // ignore existing properties so as not to affect them
    if (!trapTarget.hasOwnProperty(key)) {
    if (isNaN(value)) {
    throw new TypeError("Property must be a number.");
    }
    }

    // add the property
    return Reflect.set(trapTarget, key, value, receiver);
    }
    });

    // adding a new property
    proxy.count = 1;
    console.log(proxy.count); // 1
    console.log(target.count); // 1

    // you can assign to name because it exists on target already
    proxy.name = "proxy";
    console.log(proxy.name); // "proxy"
    console.log(target.name); // "proxy"

    // throws an error
    proxy.anotherName = "proxy";
  • Revocable Proxies

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    let target = {
    name: "target"
    };

    let { proxy, revoke } = Proxy.revocable(target, {});

    console.log(proxy.name); // "target"

    revoke();

    // throws error
    console.log(proxy.name);

Chapter13 Encapsulating Code With Modules

13.1 What are Modules?

  • Variables created in the top level of a module exist only within the top-level scope of the module.
  • 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.

13.2 Basic Exporting

  • any variables, functions, or classes that are not explicitly exported remain private to the module
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // export data
    export var color = "red";
    export let name = "Nicholas";
    export const magicNumber = 7;

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

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

13.3 Basic Importing

  • An important limitation of both export and import is that they must be used outside other statements and functions
  • 1
    2
    3
    import { identifier1, identifier2 } from "./example.js";
    import { sum } from "./example.js";
    import * as example from "./example.js";

13.4 Renaming Exports and Imports

  • 1
    2
    3
    4
    5
    function sum(num1, num2) {
    return num1 + num2;
    }

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

13.5 Default Values in Modules

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

13.6 Re-exporting a Binding

  • 1
    2
    3
    4
    import { sum } from "./example.js";
    export { sum };
    // same as
    export { sum } from "./example.js";

13.7 Importing Without Bindings

  • Some modules may only make modifications to objects in the global scope
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // module code without exports or imports
    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);
    };

13.8 Loading Modules

  • 1
    <script type="module" src="module.js"></script>
  • To support that functionality, always acts as if the defer attribute is applied

  • Modules are also executed in the order in which they appear in the HTML file
  • async
    • All resources the module needs will be downloaded before the module executes
    • can’t guarantee when the module will execute
  • Loading Modules as Workers
    • let worker = new Worker("module.js", { type: "module" });
  • Browser Module Specifier Resolution
    • 1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      // this script locates https://www.example.com/modules/module.js

      // imports from https://www.example.com/modules/example1.js
      import { first } from "./example1.js";

      // imports from https://www.example.com/example2.js
      import { second } from "../example2.js";

      // imports from https://www.example.com/example3.js
      import { third } from "/example3.js";

      // imports from https://www2.example.com/example4.js
      import { fourth } from "https://www2.example.com/example4.js";