Javascript core features

8th of January, 2018 in guide

Reading notes from You don’t know JS books.

  1. First glance
    1. Terms
      1. Common terms
      2. Statement
      3. Expression
      4. Blocks
      5. Scope
      6. Switch statement
    2. Values and types
      1. Boolean
        1. False
        2. True
    3. Comparison
      1. Equality
      2. Inequality
    4. Function scopes
      1. Hoisting
    5. Strict mode (ES5)
    6. Function expressions
    7. Immediately invoked function expressions (IIFE)
    8. Closure
      1. Example
      2. Closure usage : Module pattern
    9. this identifier
    10. Prototypes
  2. Scope & closures
    1. What is a scope
      1. Javascript compiler
        1. Traditional (simplified) compilation
        2. Javascript compilation
        3. Scope
      2. Errors
    2. Lexical scope
      1. Cheating lexical scope
    3. Function scope vs block scope
      1. Scope from functions
      2. Function as scope
        1. Anonymous functions
      3. Block as scope
        1. let
        2. const
    4. Hoisting
    5. Closures
      1. Module pattern
  3. This & object prototypes
    1. this reference
      1. Confusions
      2. Definition
      3. Rules for determining call-site
      4. Exceptions
        1. Ignored this
        2. Indirection
        3. Softening Binding
        4. Lexical this
    2. Objects
      1. Syntax
      2. Type
      3. Built-in Objects (complex primitives)
      4. Arrays
      5. Duplicating objects
      6. Property descriptors
        1. GetOwnPropertyDescriptor
        2. DefineProperty
        3. Properties
        4. Immutability
          1. Object constant
          2. Prevent Extensions
          3. Seal
          4. Freeze
        5. [[Get]]
        6. [[Put]]
        7. Getter & setter
        8. Existence
        9. Enumeration
        10. Iteration
    3. Classes
      1. Mixins (= object contents ‘mixed-in’)
        1. Explicit Mixins
          1. Parasitic inheritance
        2. Implicit Mixins
    4. [[Prototypes]]
      1. Setting & shadowing properties
      2. “Classes”
        1. Constructor
        2. Object.create
        3. Mechanics
        4. Class inheritance
        5. Inspecting “Class” Relationships (introspection / reflection)
    5. Behavior Delegation
      1. Classes vs Behavior Delegation
      2. ES6 class syntax sugar
      3. Behaviour delegation pros
    6. ES6 class syntax (pros &) cons
  4. Types & grammar
    1. Types
      1. undefined vs “undeclared”
    2. Arrays
      1. Convert array-likes
    3. Strings
    4. Numbers
      1. Small Decimal Values
        1. Safe integer values
      2. Testing for Integers
      3. 32-bit (Signed) Integers
    5. Special Values
      1. The Non-value Values
        1. Undefined
        2. void Operator
      2. Special Numbers
        1. The Not Number, Number
        2. Infinities
        3. Zeros
        4. Special equality
        5. Value vs reference
    6. Natives
      1. Internal [[Class]]
      2. Boxing wrappers
        1. Unboxing
      3. Natives as constructors
      4. Object(..), Function(..) and RegExp(..)
      5. Date(..)
      6. Error(..)
      7. Symbol(..)
      8. Native prototypes
    7. Coercion
      1. Abstract Value Operations
        1. ToString
          1. JSON Stringification
        2. ToNumber
        3. ToBoolean
          1. Falsy Values
          2. Falsy Objects
      2. Explicit Coercion
        1. Strings <=> numbers
          1. Date To number
          2. ~
        2. Explicitly: Parsing Numeric Strings
          1. Parsing Non-Strings
        3. Everything => Boolean
      3. Implicit Coercion
        1. Strings <–> Numbers
        2. Booleans –> Numbers
        3. * –> Boolean
      4. Operators || and &&
      5. Symbol Coercion
      6. Loose Equals vs. Strict Equals
        1. Abstract equality
          1. Comparing: strings to numbers
          2. Comparing: anything to boolean
          3. Comparing: nulls to undefineds
          4. Comparing: objects to non-objects
          5. Edge Cases
          6. Safely Using Implicit Coercion
        2. Abstract Relational Comparison
      7. Other oerators
    8. Grammar
      1. Statements & expressions
        1. Statement Completion Values
        2. Expression Side Effects
        3. Contextual Rules
          1. { .. } Curly Braces
        4. Operator Precedence
          1. Short circuited
        5. Automatic Semicolon Insertion (ASI)
        6. Errors
      2. try..finally
      3. switch
      4. How to code on legacy environmnents
  5. Async & performance
    1. Event Loop
    2. Parallel Threading
      1. Run to completion
      2. Concurrency
      3. Noninteracting
      4. Interaction
      5. Cooperation
    3. Jobs
    4. Callbacks
      1. Nested/Chained Callbacks
      2. Trust issues
      3. Trying to Save Callbacks
    5. Promises
      1. Future value
      2. Completion event
      3. Promise “Events”
      4. Thenable Duck Typing
      5. Promise Trust
        1. Calling Too Early
        2. Calling Too Late
        3. Never Calling the Callback
        4. Calling Too Few or Too Many Times
        5. Failing to Pass Along Any Parameters/Environment
        6. Swallowing Any Errors/Exceptions
        7. Trustable Promise?
        8. Trust build
      6. Chain Flow
      7. Terminology: Resolve, Fulfill, and Reject
      8. Error Handling
      9. Uncaught errors
      10. Promise Patterns
        1. Promise.all([ .. ])
        2. Promise.race([ .. ])
        3. “Finally”
        4. Variations on all([ .. ]) and race([ .. ])
      11. Promise limitations
        1. Sequence Error Handling
        2. Single value
        3. Single Resolution
        4. Inertia
        5. Promise Uncancelable
        6. Promise Performance
    6. Generators
      1. Breaking Run-to-Completion
        1. Input and Output
          1. Iteration messaging
          2. Multiple iterators
      2. Iterators & iterables
      3. Holy grail ! Handle asynchronous requests through a synchronous flow
      4. Synchronous Error Handling
      5. Generators + Promises
        1. Promises, Hidden
      6. Generator Delegation
        1. Delegating messages
        2. Error delegations
      7. TL;DR
    7. performance
      1. Web workers
        1. Worker Environment
        2. Data transfer
      2. Review
    8. Benchmark & performance
      1. Repetition
      2. Benchmark.js
      3. jsPerf.com

First glance

Terms

Common terms

Statement

A group of words, numbers, and operators that performs a specific task is a statement a = b * 2;

Expression

An expression is any reference to a variable or value, or a set of variable(s) and value(s) combined with operators.

Blocks

In JavaScript, a block is defined by wrapping one or more statements inside a curly-brace pair { .. }

Scope

Each function gets its own scope.

Switch statement

1
2
3
4
5
6
7
8
9
10
11
switch (a) {
    case 2:
    case 10:
        // some cool stuff
        break;
    case 42:
        // other stuff
        break;
    default:
        // fallback
}

Values and types

Boolean

False

Comparison

Equality

Note that comparing two non-primitive values (e.g. objects (+ arrays & functions)) only checks the object’s reference, not the underlying values

1
2
3
4
5
6
7
var a = [1,2,3];
var b = [1,2,3];
var c = "1,2,3";
 
a == c;     // true, coercion applied on the array
b == c;     // true, coercion applied on the array
a == b;     // false

Inequality

1
2
3
4
5
6
var a = 41;
var b = "42";
var c = "43";
 
a < b;      // true
b < c;      // true
1
2
3
4
5
6
var a = 42;
var b = "foo";
 
a < b;      // false
a > b;      // false
a == b;     // false

Function scopes

Hoisting

Strict mode (ES5)

Tightens the rules for certain behaviors => safer & cleaner. Allows better optimization.

Function expressions

1
2
3
4
5
6
7
var foo = function() {     // Anonymous function expression
    // ..
};
 
var x = function bar(){    // Named function expression
    // ..
};

Immediately invoked function expressions (IIFE)

1
2
3
4
5
6
7
8
9
(function () { /* ... */ })();
(function () { /* ... */ }());
(() => { /* ... */ })();
 
!function () { /* ... */ }();
~function () { /* ... */ }();
-function () { /* ... */ }();
+function () { /* ... */ }();
void function () { /* ... */ }();

Closure

Example

Think of closure as a way to “remember” and continue to access a function’s scope (its variables) even once the function has finished running.

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
function makeAdder(x) {
    // parameter `x` is an inner variable
 
    // inner function `add()` uses `x`, so
    // it has a "closure" over it
    function add(y) {
        return y + x;
    };
 
    return add;
}
 
// `plusOne` gets a reference to the inner `add(..)`
// function with closure over the `x` parameter of
// the outer `makeAdder(..)`
var plusOne = makeAdder( 1 );
 
// `plusTen` gets a reference to the inner `add(..)`
// function with closure over the `x` parameter of
// the outer `makeAdder(..)`
var plusTen = makeAdder( 10 );
 
plusOne( 3 );       // 4  <-- 1 + 3
plusOne( 41 );      // 42 <-- 1 + 41
 
plusTen( 13 );      // 23 <-- 10 + 13

Closure usage : Module pattern

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function User(){
    var username, password;
 
    function doLogin(user,pw) {
        username = user;
        password = pw;
 
        // do the rest of the login work
    }
 
    var publicAPI = {
        login: doLogin
    };
 
    return publicAPI;
}
 
// create a `User` module instance
var fred = User();
 
fred.login( "fred", "12Battery34!" );

this identifier

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function foo() {
    console.log( this.bar );
}
 
var bar = "global";
 
var obj1 = {
    bar: "obj1",
    foo: foo
};
 
var obj2 = {
    bar: "obj2"
};
 
// --------
 
foo();              // "global", set this to the global object in non strict mode, undefined otherwise
obj1.foo();         // "obj1", set this to obj1
foo.call( obj2 );       // "obj2", set this to obj2
new foo();          // undefined, set this to brand new object

Prototypes

When you reference a property on an object, if that property doesn’t exist, JavaScript will automatically use that object’s internal prototype reference to find another object to look for the property on. You could think of this almost as a fallback if the property is missing. The internal prototype reference linkage from one object to its fallback happens at the time the object is created.

1
2
3
4
5
6
7
8
9
10
11
var foo = {
    a: 42
};
 
// create `bar` and link it to `foo`
var bar = Object.create( foo );
 
bar.b = "hello world";
 
bar.b;      // "hello world"
bar.a;      // 42 <-- delegated to `foo`

Used for behaviour delegation.

Scope & closures

What is a scope

Javascript compiler

Traditional (simplified) compilation
Javascript compilation
Scope

RHS : Right Hand Side (source of the assignment) LHS : Left Hand Side (target of the assignment)

1
2
3
4
5
6
function foo(a) {
    var b = a;
    return a + b;
}
 
var c = foo( 2 );

Identify all the LHS look-ups (there are 3!)

Errors

Lexical scope

Cheating lexical scope

Function scope vs block scope

Scope from functions

Used for

Function as scope

Anonymous functions

These functions has no name

Block as scope

Block scoped statements :

Hoisting

1
2
console.log( a );   // undefined
var a = 2;

All declarations, both variables and functions, are processed first, before any part of your code is executed var a = 2; is processed as two statements : var a and a = 2;. First statement is processed during the compilation phase. The second statement is left in place for the execution phase.

Closures

Closure is when a function is able to remember and access its lexical scope even when that function is executing outside its lexical scope.

1
2
3
4
5
6
7
8
9
10
11
12
13
function foo() {
    var a = 2;
 
    function bar() {
        console.log( a );
    }
 
    return bar;
}
 
var baz = foo();
 
baz(); // 2 -- Whoa, closure was just observed, man.

Whatever facility we use to transport an inner function outside of its lexical scope, it will maintain a scope reference to where it was originally declared, and wherever we execute it, that closure will be exercised.

1
2
3
4
5
6
7
8
9
10
11
12
for (var i=1; i<=5; i++) {
    setTimeout( function timer(){
      console.log( i );     // display 6 five times. Only one scope here
    }, i*1000 );
}
 
for (var i=1; i<=5; i++) {
    let j = i;
    setTimeout( function timer(){
      console.log( j );     // works as intended because let var created a different scope at each iteration
    }, j*1000 );
}

Module pattern

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function CoolModule() {
    var something = "cool";
    var another = [1, 2, 3];
 
    function doSomething() {
        console.log( something );
    }
 
    function doAnother() {
        console.log( another.join( " ! " ) );
    }
 
    return {
        doSomething: doSomething,
        doAnother: doAnother
    };
}
 
var foo = CoolModule();
 
foo.doSomething(); // cool
foo.doAnother(); // 1 ! 2 ! 3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var foo = (function CoolModule() {
    var something = "cool";
    var another = [1, 2, 3];
 
    function doSomething() {
        console.log( something );
    }
 
    function doAnother() {
        console.log( another.join( " ! " ) );
    }
 
    return {
        doSomething: doSomething,
        doAnother: doAnother
    };
})();
 
foo.doSomething(); // cool
foo.doAnother(); // 1 ! 2 ! 3

This & object prototypes

this reference

Confusions

1
var bar = new foo()
  1. Is the function called with call or apply (explicit binding), even hidden inside a bind hard binding? If so, this is the explicitly specified object.
1
var bar = foo.call( obj2 )
  1. Is the function called with a context (implicit binding), otherwise known as an owning or containing object? If so, this is that context object.
1
var bar = obj1.foo()
  1. Otherwise, default the this (default binding). If in strict mode, pick undefined, otherwise pick the global object.
1
var bar = foo()

Exceptions

Ignored this
1
2
3
4
5
6
7
8
9
10
function foo(a,b) {
    console.log( "a:" + a + ", b:" + b );
}
 
// spreading out array as parameters
foo.apply( null, [2, 3] ); // a:2, b:3
 
// currying with `bind(..)`
var bar = foo.bind( null, 2 );
bar( 3 ); // a:2, b:3

Use spread operator ... in ES6

1
foo(...[1,2])
Indirection

Another thing to be aware of is you can (intentionally or not!) create “indirect references” to functions, and in those cases, when that function reference is invoked, the default binding rule also applies.

Softening Binding

Provides a different default for default binding (not global or undefined), while still leaving the function able to be manually this bound via implicit binding or explicit binding techniques

Lexical this

Arrow-functions : Instead of using the four standard this rules, arrow-functions adopt the this binding from the enclosing (function or global) scope (= use of lexical scope for this). this is resolved at call-time Lose this flexibility, avoid arrow functions

Objects

Syntax

Objects come in two forms: the declarative (literal) form, and the constructed form.

1
2
3
4
5
6
7
8
9
// Declarative form:
var myObj = {
    key: value
    // ...
};
 
// Constructed form:
var myObj = new Object();
myObj.key = value;

Note: It’s extremely uncommon to use the “constructed form” for creating objects as just shown. You would pretty much always want to use the literal syntax form. The same will be true of most of the built-in objects (see below)

Type

6 primary types

Simple primitives (string, number, boolean, null, and undefined) are not themselves objects

Built-in Objects (complex primitives)

The language automatically coerces a “string” primitive to a String object when necessary null and undefined have no object wrapper form, only their primitive values. By contrast, Date values can only be created with their constructed object form, as they have no literal form counter-part.

For Objects, only use the constructed form if you need the extra options

Objects property names are always strings.

ES6 adds computed property names, where you can specify an expression, surrounded by a [ ] pair, in the key-name position of an object-literal declaration:

1
2
3
4
5
6
7
8
9
var prefix = "foo";
 
var myObject = {
    [prefix + "bar"]: "hello",
    [prefix + "baz"]: "world"
};
 
myObject["foobar"]; // hello
myObject["foobaz"]; // world

Technically, functions never “belong” to objects, so saying that a function that just happens to be accessed on an object reference is automatically a “method” seems a bit of a stretch of semantics.

Every time you access a property on an object, that is a property access

Arrays

1
2
3
4
5
6
7
var myArray = [ "foo", 42, "bar" ];
 
myArray["3"] = "baz";
 
myArray.length; // 4
 
myArray[3];     // "baz"

Duplicating objects

Shallow or deep copy ???

1
var newObj = JSON.parse( JSON.stringify( someObj ) );

Property descriptors

GetOwnPropertyDescriptor
1
2
3
4
5
6
7
8
9
10
11
var myObject = {
    a: 2
};
 
Object.getOwnPropertyDescriptor( myObject, "a" );
// {
//    value: 2,
//    writable: true,
//    enumerable: true,
//    configurable: true
// }
DefineProperty
1
2
3
4
5
6
7
8
9
10
var myObject = {};
 
Object.defineProperty( myObject, "a", {
    value: 2,
    writable: true,
    configurable: true,
    enumerable: true
} );
 
myObject.a; // 2
Properties
Immutability
Object constant

By combining writable:false and configurable:false, you can essentially create a constant (cannot be changed, redefined or deleted) as an object property, like

1
2
3
4
5
6
7
var myObject = {};
 
Object.defineProperty( myObject, "FAVORITE_NUMBER", {
    value: 42,
    writable: false,
    configurable: false
} );
Prevent Extensions

If you want to prevent an object from having new properties added to it, but otherwise leave the rest of the object’s properties alone, call Object.preventExtensions(..)

1
2
3
4
5
6
7
8
var myObject = {
    a: 2
};
 
Object.preventExtensions( myObject );
 
myObject.b = 3;
myObject.b; // undefined
Seal

Object.seal(..) creates a “sealed” object, which means it takes an existing object and essentially calls Object.preventExtensions(..) on it, but also marks all its existing properties as configurable:false.

So, not only can you not add any more properties, but you also cannot reconfigure or delete any existing properties (though you can still modify their values).

Freeze

Object.freeze(..) creates a frozen object, which means it takes an existing object and essentially calls Object.seal(..) on it, but it also marks all “data accessor” properties as writable:false, so that their values cannot be changed.

[[Get]]
[[Put]]

If the property is present, the [[Put]] algorithm will roughly check:

Getter & setter

Property accessors introduced with ES5. When used, value and writable property descriptors are ignored.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var myObject = {
    // define a getter for `a`
    get a() {
        return 2;
    }
};
 
Object.defineProperty(
    myObject,   // target
    "b",        // property name
    {           // descriptor
        // define a getter for `b`
        get: function(){ return this.a * 2 },
 
        // make sure `b` shows up as an object property
        enumerable: true
    }
);
 
myObject.a; // 2
myObject.b; // 4
Existence

in operator (look up the [[Prototype]] chain or myObject.hasOwnProperty(), on direct object. If myObject created as an empty object (Object.create(null)), use Object.prototype.hasOwnProperty.call(myObject,"a")

Enumeration

for..in : loop through enumerable properties myObject.propertyIsEnumerable('a') Object.keys(myObject) : returns an array of all enumerable properties Object.getOwnPropertyNames(myObject) : returns an array of all properties, enumerable or not

Iteration

ES5 helpers for arrays : myArray.forEach(): callback returning all values myArray.every(): callback returning values. Stops when callback return false; myArray.some(): callback returning values. Stops when callback return true;

Iterating over Object properties do not guarantee their order !!!

ES6 : for..of : iterate over the values instead of the keys. Arrays has a built-in @@iterator Symbol (Symbol.iterator)

1
2
3
4
5
6
7
var myArray = [ 1, 2, 3 ];
var it = myArray[Symbol.iterator]();
 
it.next(); // { value:1, done:false }
it.next(); // { value:2, done:false }
it.next(); // { value:3, done:false }
it.next(); // { done:true }

Classes

Mixins (= object contents ‘mixed-in’)

Explicit Mixins

Explicit pseudo polymorphism:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var Vehicle = {
    engines: 1,
 
    ignition: function() {
        console.log( "Turning on my engine." );
    },
 
    drive: function() {
        this.ignition();
        console.log( "Steering and moving forward!" );
    }
};
 
var Car = mixin( Vehicle, {
    wheels: 4,
 
    drive: function() {
        Vehicle.drive.call( this );
        console.log( "Rolling on all " + this.wheels + " wheels!" );
    }
} );

Here we need to call the Vehicle drive function with Vehicle.drive.call( this ); because the drive function defined on Car shadowed the drive function defined on Vehicle

Parasitic inheritance
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
 // "Traditional JS Class" `Vehicle`
function Vehicle() {
    this.engines = 1;
}
Vehicle.prototype.ignition = function() {
    console.log( "Turning on my engine." );
};
Vehicle.prototype.drive = function() {
    this.ignition();
    console.log( "Steering and moving forward!" );
};
 
// "Parasitic Class" `Car`
function Car() {
    // first, `car` is a `Vehicle`
    var car = new Vehicle();
 
    // now, let's modify our `car` to specialize it
    car.wheels = 4;
 
    // save a privileged reference to `Vehicle::drive()`
    var vehDrive = car.drive;
 
    // override `Vehicle::drive()`
    car.drive = function() {
        vehDrive.call( this );
        console.log( "Rolling on all " + this.wheels + " wheels!" );
    };
 
    return car;
}
 
var myCar = new Car();
 
myCar.drive();
// Turning on my engine.
// Steering and moving forward!
// Rolling on all 4 wheels!
Implicit Mixins
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var Something = {
    cool: function() {
        this.greeting = "Hello World";
        this.count = this.count ? this.count + 1 : 1;
    }
};
 
Something.cool();
Something.greeting; // "Hello World"
Something.count; // 1
 
var Another = {
    cool: function() {
        // implicit mixin of `Something` to `Another`
        Something.cool.call( this );
    }
};
 
Another.cool();
Another.greeting; // "Hello World"
Another.count; // 1 (not shared state with `Something`)

[[Prototypes]]

Objects in JavaScript have an internal property, denoted in the specification as [[Prototype]], which is simply a reference to another object

Some utilities found here you may be familiar with include .toString() and .valueOf(). In Chapter 3, we introduced another: .hasOwnProperty(..). And yet another function on Object.prototype you may not be familiar with, but which we’ll address later in this chapter, is .isPrototypeOf(..)

Setting & shadowing properties

1
myObject.foo = "bar";

If the myObject object already has a normal data accessor property called foo directly present on it, the assignment is as simple as changing the value of the existing property. If foo is not already present directly on myObject, the [[Prototype]] chain is traversed, just like for the [[Get]] operation.

  1. If foo is not found anywhere in the chain, the property foo is added directly to myObject with the specified value, as expected.
  2. If a normal data accessor property named foo is found anywhere higher on the [[Prototype]] chain, and it’s not marked as read-only (writable:false) then a new property called foo is added directly to myObject, resulting in a shadowed property
  3. If a foo is found higher on the [[Prototype]] chain, but it’s marked as read-only (writable:false), then both the setting of that existing property as well as the creation of the shadowed property on myObject are disallowed. If the code is running in strict mode, an error will be thrown. Otherwise, the setting of the property value will silently be ignored. Either way, no shadowing occurs.
  4. If a foo is found higher on the [[Prototype]] chain and it’s a setter (see Chapter 3), then the setter will always be called. No foo will be added to (aka, shadowed on) myObject, nor will the foo setter be redefined.

“Classes”

All functions by default get a public, non-enumerable (see Chapter 3) property on them called prototype, which points at an otherwise arbitrary object new Foo() results in a new object (we called it a), and that new object a is internally [[Prototype]] linked to the Foo.prototype object.

Prototypal inheritance : In JavaScript, we don’t make copies from one object (“class”) to another (“instance”). We make links between objects. For the [[Prototype]] mechanism, visually, the arrows move from right to left, and from bottom to top. “Inheritance” implies a copy operation, and JavaScript doesn’t copy object properties (natively, by default). Instead, JS creates a link between two objects, where one object can essentially delegate property/function access to another object. “Delegation” (see Chapter 6) is a much more accurate term for JavaScript’s object-linking mechanism

If you try to think of any given object in JS as the sum total of all behavior that is available via delegation, and in your mind you flatten all that behavior into one tangible thing, then you can (sorta) see how “differential inheritance” might fit But just like with “prototypal inheritance”, “differential inheritance” pretends that your mental model is more important than what is physically happening in the language. It overlooks the fact that object B is not actually differentially constructed, but is instead built with specific characteristics defined, alongside “holes” where nothing is defined. It is in these “holes” (gaps in, or lack of, definition) that delegation can take over and, on the fly, “fill them in” with delegated behavior.

Constructor

Functions aren’t constructors, but function calls are “constructor calls” if and only if new is used. Constructor call constructs an object AND executes the associated function.

For one, the .constructor property on Foo.prototype is only there by default on the object created when Foo the function is declared. If you create a new object, and replace a function’s default .prototype object reference, the new object will not by default magically get a .constructor on it.

The fact is, .constructor on an object arbitrarily points, by default, at a function who, reciprocally, has a reference back to the object – a reference which it calls .prototype

.constructor is extremely unreliable, and an unsafe reference to rely upon in your code. Generally, such references should be avoided where possible.

Object.create

Object.create(prototype) creates a “new” object out of thin air, and links that new object’s internal [[Prototype]] to the object you specify

Mechanics

Delegation through the [[Prototype]] mimics the class inheritage behaviour

1
2
3
4
5
6
7
8
9
10
11
12
13
function Foo(name) {
    this.name = name;
}
 
Foo.prototype.myName = function() {
    return this.name;
};
 
var a = new Foo( "a" );
var b = new Foo( "b" );
 
a.myName(); // "a"
b.myName(); // "b"

But in that example, the property myName is not copied over a and b ; it is carried by Foo.prototype and resolved through [[Prototype]] delegation

Class inheritance
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
function Foo(name) {
    this.name = name;
}
 
Foo.prototype.myName = function() {
    return this.name;
};
 
function Bar(name,label) {
    Foo.call( this, name );
    this.label = label;
}
 
// here, we make a new `Bar.prototype`
// linked to `Foo.prototype`
Bar.prototype = Object.create( Foo.prototype );
 
// Beware! Now `Bar.prototype.constructor` is gone,
// and might need to be manually "fixed" if you're
// in the habit of relying on such properties!
 
Bar.prototype.myLabel = function() {
    return this.label;
};
 
var a = new Bar( "a", "obj a" );
 
a.myName(); // "a"
a.myLabel(); // "obj a"

What’s important here is the Bar.prototype = Object.create( Foo.prototype ); line. We replace Bar.prototype with a new object [[Prototype]] linked to Foo.prototype. It permits to link Bar to Foo without calling Foo constructor (as in Bar.prototype = new Foo()); It also creates a brand new object linked through [[Prototype]], not just a new reference (as in Bar.prototype = Foo.prototype;).

1
2
3
4
5
6
7
8
ES6 introduces a new way to handle this problem conveniently : 
// pre-ES6
// throws away default existing `Bar.prototype`
Bar.prototype = Object.create( Foo.prototype );
 
// ES6+
// modifies existing `Bar.prototype`
Object.setPrototypeOf( Bar.prototype, Foo.prototype );
Inspecting “Class” Relationships (introspection / reflection)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// helper utility to see if `o1` is
// related to (delegates to) `o2`
function isRelatedTo(o1, o2) {
    function F(){}
    F.prototype = o2;
    return o1 instanceof F;
}
 
var a = {};
var b = Object.create( a );
 
isRelatedTo( b, a ); // true
 
a.isPrototypeOf( b ); // true

Behavior Delegation

Classes vs Behavior Delegation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function Foo(who) {
    this.me = who;
}
Foo.prototype.identify = function() {
    return "I am " + this.me;
};
 
function Bar(who) {
    Foo.call( this, who );
}
Bar.prototype = Object.create( Foo.prototype );
 
Bar.prototype.speak = function() {
    alert( "Hello, " + this.identify() + "." );
};
 
var b1 = new Bar( "b1" );
var b2 = new Bar( "b2" );
 
b1.speak();
b2.speak();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var Foo = {
    init: function(who) {
        this.me = who;
    },
    identify: function() {
        return "I am " + this.me;
    }
};
 
var Bar = Object.create( Foo );
 
Bar.speak = function() {
    alert( "Hello, " + this.identify() + "." );
};
 
var b1 = Object.create( Bar );
b1.init( "b1" );
var b2 = Object.create( Bar );
b2.init( "b2" );
 
b1.speak();
b2.speak();

Behaviour delegation tends to avoid conflicting function names (shadowing needing ugly polymorphism).

ES6 class syntax sugar

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Foo{
  constructor (who){
    this.me = who;
  }
  identify () {
    return "I am " + this.me;
  }
}
 
class Bar extends Foo {
  constructor(who){
    super(who);
  }
  speak () {
    alert( "Hello, " + this.identify() + "." );
  }
}
 
var b1 = new Bar( "b1" );
var b2 = new Bar( "b2" );
 
b1.speak();
b2.speak();

Behaviour delegation pros

ES6 class syntax (pros &) cons

pros :

cons:

Types & grammar

Types

Understanding types is important to understand coercion (conversion) 7 types :

All of these types except object are called “primitives”.

The typeof operator inspects the type of the given value, and always returns one of seven string values – surprisingly, there’s not an exact 1-to-1 match with the seven built-in types we just listed.

typeof null returns 'object' (legacy bug kept for compatibility)

Functions are objects that as an internal [[Call]] property that allows it to be invoked.

The function object has a length property set to the number of formal parameters it is declared with.

In JavaScript, variables don’t have types – values have types. Variables can hold any value, at any time.

undefined vs “undeclared”

1
2
3
4
var a;
 
a; // undefined
b; // ReferenceError: b is not defined

Here b is undeclared.

typeof has a safety guard for undeclared variables. Thus it can be used to safety check the existence of a variable:

1
2
3
4
5
6
7
8
9
// oops, this would throw an error!
if (DEBUG) {
    console.log( "Debugging is starting" );
}
  
// this is a safe existence check
if (typeof DEBUG !== "undefined") {
    console.log( "Debugging is starting" );
}

Arrays

Convert array-likes

1
2
3
4
5
6
7
function foo() {
    var arr = Array.prototype.slice.call( arguments );
    arr.push( "bam" );
    console.log( arr );
}
 
foo( "bar", "baz" ); // ["bar","baz","bam"]
1
var arr = Array.from( arguments );

Strings

A string is not an array of characters Strings do have a shallow resemblance to arrays – array-likes, as above – for instance, both of them having a length property, an indexOf(..) method (array version only as of ES5), and a concat(..) method:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var a = "foo";
var b = ["f","o","o"];
 
a.length;               // 3
b.length;               // 3
 
a.indexOf( "o" );           // 1
b.indexOf( "o" );           // 1
 
var c = a.concat( "bar" );      // "foobar"
var d = b.concat( ["b","a","r"] );  // ["f","o","o","b","a","r"]
 
a === c;                // false
b === d;                // false
 
a;                  // "foo"
b;                  // ["f","o","o"]
 
a[1] = "O";
b[1] = "O";
 
a; // "foo"
b; // ["f","O","o"]

Strings are immutable while arrays are quite mutable. a[1] form is not always valid in JS (prefer charAt(1))

Also, many of the array methods that could be helpful when dealing with strings are not actually available for them, but we can “borrow” non-mutation array methods against our string:

1
2
3
4
5
6
7
8
9
10
a.join;         // undefined
a.map;          // undefined
 
var c = Array.prototype.join.call( a, "-" );
var d = Array.prototype.map.call( a, function(v){
    return v.toUpperCase() + ".";
} ).join( "" );
 
c;              // "f-o-o"
d;              // "F.O.O."

Numbers

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// invalid syntax:
42.toFixed( 3 );    // SyntaxError because 42. = 42.0
 
// these are all valid:
(42).toFixed( 3 );  // "42.000"
0.42.toFixed( 3 );  // "0.420"
42..toFixed( 3 );   // "42.000"
42 .toFixed(3); // "42.000"
 
var onethousand = 1E3;              // means 1 * 10^3
var onemilliononehundredthousand = 1.1E6;   // means 1.1 * 10^6
 
0o363;      // octal for: 243
 
0b11110011; // binary for: 243

Small Decimal Values

The most (in)famous side effect of using binary floating-point numbers (which, remember, is true of all languages that use IEEE 754 – not just JavaScript as many assume/pretend) is:

1
0.1 + 0.2 === 0.3; // false

The most commonly accepted practice is to use a tiny “rounding error” value as the tolerance for comparison. This tiny value is often called “machine epsilon,” which is commonly 2^-52 (2.220446049250313e-16) for the kind of numbers in JavaScript.

As of ES6, Number.EPSILON is predefined with this tolerance value, so you’d want to use it, but you can safely polyfill the definition for pre-ES6:

1
2
3
4
5
6
7
8
9
10
11
12
13
if (!Number.EPSILON) {
    Number.EPSILON = Math.pow(2,-52);
}
 
function numbersCloseEnoughToEqual(n1,n2) {
    return Math.abs( n1 - n2 ) < Number.EPSILON;
}
 
var a = 0.1 + 0.2;
var b = 0.3;
 
numbersCloseEnoughToEqual( a, b );          // true
numbersCloseEnoughToEqual( 0.0000001, 0.0000002 );  // false
Safe integer values

2^53 - 1 ES6 : Number.MAX_SAFE_INTEGER & Number.MIN_SAFE_INTEGER 64-bit numbers cannot be represented accurately with the number type, so must be stored in (and transmitted to/from) JavaScript using string representation

Testing for Integers

ES6 Number.isInteger( .. )

1
2
3
Number.isInteger( 42 );     // true
Number.isInteger( 42.000 ); // true
Number.isInteger( 42.3 );   // false

To test if a value is a safe integer, use the ES6-specified Number.isSafeInteger(..)

1
2
3
Number.isSafeInteger( Number.MAX_SAFE_INTEGER );    // true
Number.isSafeInteger( Math.pow( 2, 53 ) );      // false
Number.isSafeInteger( Math.pow( 2, 53 ) - 1 );      // true

32-bit (Signed) Integers

While integers can range up to roughly 9 quadrillion safely (53 bits), there are some numeric operations (like the bitwise operators) that are only defined for 32-bit numbers, so the “safe range” for numbers used in that way must be much smaller.

The range then is Math.pow(-2,31) (-2147483648, about -2.1 billion) up to Math.pow(2,31)-1 (2147483647, about +2.1 billion).

Special Values

The Non-value Values

Undefined

In non-strict mode, it’s actually possible (though incredibly ill-advised!) to assign a value to the globally provided undefined identifier. In both non-strict mode and strict mode, however, you can create a local variable of the name undefined. But again, this is a terrible idea!

void Operator

Returns undefined whatever the right operand is void 0, void 1, void true and undefined

In general, if there’s ever a place where a value exists (from some expression) and you’d find it useful for the value to be undefined instead, use the void operator.

Special Numbers

The Not Number, Number

Any mathematic operation you perform without both operands being numbers (or values that can be interpreted as regular numbers in base 10 or base 16) will result in the operation failing to produce a valid number, in which case you will get the NaN value. It would be much more accurate to think of NaN as being “invalid number,” “failed number,” or even “bad number,” than to think of it as “not a number.”

1
2
3
4
var a = 2 / "foo";
 
a == NaN;   // false
a === NaN;  // false

How to test for NaN

1
2
3
4
5
6
7
8
9
10
11
12
13
var a = 2 / "foo";
 
isNaN( a ); // true
 
// hold on ....
var c = 2 / "foo";
var b = "foo";
 
c; // NaN
b; // "foo"
 
window.isNaN( c ); // true
window.isNaN( b ); // true -- ouch!
Infinities

Number.POSITIVE_INFINITY and Number.NEGATIVE_INFINITY

1
2
var a = 1 / 0;  // Infinity
var b = -1 / 0; // -Infinity
1
2
3
4
var a = Number.MAX_VALUE;   // 1.7976931348623157e+308
a + a;                      // Infinity
a + Math.pow( 2, 970 );     // Infinity
a + Math.pow( 2, 969 );     // 1.7976931348623157e+308

According to the specification, if an operation like addition results in a value that’s too big to represent, the IEEE 754 “round-to-nearest” mode specifies what the result should be. So, in a crude sense, Number.MAX_VALUE + Math.pow( 2, 969 ) is closer to Number.MAX_VALUE than to Infinity, so it “rounds down,” whereas Number.MAX_VALUE + Math.pow( 2, 970 ) is closer to Infinity so it “rounds up”.

Once you overflow to either one of the infinities, however, there’s no going back. In other words, in an almost poetic sense, you can go from finite to infinite but not from infinite back to finite.

Infinity / Infinity is not a defined operation. In JS, this results in NaN

Zeros

JavaScript has both a normal zero 0 (otherwise known as a positive zero +0) and a negative zero -0 Addition and subtraction cannot result in a negative zero. However, if you try to stringify a negative zero value, it will always be reported as “0”, according to the spec.

1
2
3
4
5
6
7
8
9
10
11
var a = 0;
var b = 0 / -3;
 
a == b;     // true
-0 == 0;    // true
 
a === b;    // true
-0 === 0;   // true
 
0 > -0;     // false
a > b;      // false

Now, why do we need a negative zero, besides academic trivia?

There are certain applications where developers use the magnitude of a value to represent one piece of information (like speed of movement per animation frame) and the sign of that number to represent another piece of information (like the direction of that movement).

In those applications, as one example, if a variable arrives at zero and it loses its sign, then you would lose the information of what direction it was moving in before it arrived at zero. Preserving the sign of the zero prevents potentially unwanted information loss.

Special equality

As of ES6, there’s a new utility that can be used to test two values for absolute equality, without any of these exceptions. It’s called Object.is(..):

1
2
3
4
5
6
var a = 2 / "foo";
var b = -3 * 0;
 
Object.is( a, NaN );    // true
Object.is( b, -0 ); // true
Object.is( b, 0 );  // false

Object.is(..) probably shouldn’t be used in cases where == or === are known to be safe (see Chapter 4 “Coercion”), as the operators are likely much more efficient and certainly are more idiomatic/common

Value vs reference

A reference in JS points at a (shared) value, so if you have 10 different references, they are all always distinct references to a single shared value; none of them are references/pointers to each other.

1
2
3
4
5
6
7
8
9
10
11
var a = 2;
var b = a; // `b` is always a copy of the value in `a`
b++;
a; // 2
b; // 3
 
var c = [1,2,3];
var d = c; // `d` is a reference to the shared `[1,2,3]` value
d.push( 4 );
c; // [1,2,3,4]
d; // [1,2,3,4]

Simple values (aka scalar primitives) are always assigned/passed by value-copy: null, undefined, string, number, boolean, and ES6’s symbol.

Compound values – objects (including arrays, and all boxed object wrappers – see Chapter 3) and functions – always create a copy of the reference on assignment or passing.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function foo(x) {
    x.push( 4 );
    x; // [1,2,3,4]
 
    // later
    x = [4,5,6];
    x.push( 7 );
    x; // [4,5,6,7]
}
 
var a = [1,2,3];
 
foo( a );
 
a; // [1,2,3,4]  not  [4,5,6,7]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function foo(x) {
    x.push( 4 );
    x; // [1,2,3,4]
 
    // later
    x.length = 0; // empty existing array in-place
    x.push( 4, 5, 6, 7 );
    x; // [4,5,6,7]
}
 
var a = [1,2,3];
 
foo( a );
 
a; // [4,5,6,7]  not  [1,2,3,4]

To effectively pass a compound value (like an array) by value-copy, you need to manually make a copy of it, so that the reference passed doesn’t still point to the original. For example foo( a.slice() );

References are not like references/pointers in other languages – they’re never pointed at other variables/references, only at the underlying values.

Natives

Natives can be used as a constructor, however, the result can be surprising :

1
2
3
4
5
6
7
var a = new String( "abc" );
 
typeof a; // "object" ... not "String"
 
a instanceof String; // true
 
Object.prototype.toString.call( a ); // "[object String]"

Internal [[Class]]

Objects subtypes have an internal property [[Class]] (for classification). It can’t be accessed directly but can be revealed with the function Object.prototype.toString(..).

1
2
3
4
5
6
7
8
9
// null and undefined primitives
Object.prototype.toString.call( null );     // "[object Null]"
Object.prototype.toString.call( undefined );  // "[object Undefined]"
 
 
// Other primitives are 'boxed'
Object.prototype.toString.call( "abc" );  // "[object String]"
Object.prototype.toString.call( 42 );   // "[object Number]"
Object.prototype.toString.call( true );   // "[object Boolean]"

Boxing wrappers

The object wrappers serve a very important purpose. Primitive values don’t have properties or methods, so to access .length or .toString() you need an object wrapper around the value. Thankfully, JS will automatically box (aka wrap) the primitive value to fulfill such accesses.

Unboxing

Use the valueOf() method.

Natives as constructors

For array, object, function, and regular-expression values, it’s almost universally preferred that you use the literal form for creating the values, but the literal form creates the same sort of object as the constructor form does (that is, there is no nonwrapped value).

1
2
3
4
5
var a = new Array( 1, 2, 3 );
a; // [1, 2, 3]
 
var b = [1, 2, 3];
b; // [1, 2, 3]

Object(..), Function(..) and RegExp(..)

There’s practically no reason to ever use the new Object() constructor form, especially since it forces you to add properties one-by-one instead of many at once in the object literal form.

The Function constructor is helpful only in the rarest of cases, where you need to dynamically define a function’s parameters and/or its function body. Do not just treat Function(..) as an alternate form of eval(..). You will almost never need to dynamically define a function in this way.

Regular expressions defined in the literal form (/^a*b+/g) are strongly preferred, not just for ease of syntax but for performance reasons – the JS engine precompiles and caches them before code execution. Unlike the other constructor forms we’ve seen so far, RegExp(..) has some reasonable utility: to dynamically define the pattern for a regular expression.

Date(..)

The Date(..) and Error(..) native constructors are much more useful than the other natives, because there is no literal form for either. To create a date object value, you must use new Date(). But an even easier way is to just call the static helper function defined as of ES5: Date.now()

Error(..)

The Error(..) constructor (much like Array() above) behaves the same with the new keyword present or omitted. The main reason you’d want to create an error object is that it captures the current execution stack context into the object (in most JS engines, revealed as a read-only .stack property once constructed). This stack context includes the function call-stack and the line-number where the error object was created, which makes debugging that error much easier.

Technically, in addition to the general Error(..) native, there are several other specific-error-type natives: EvalError(..), RangeError(..), ReferenceError(..), SyntaxError(..), TypeError(..), and URIError(..). But it’s very rare to manually use these specific error natives. They are automatically used if your program actually suffers from a real exception (such as referencing an undeclared variable and getting a ReferenceError error).

Symbol(..)

New as of ES6, an additional primitive value type has been added, called Symbol. Symbols are special “unique” (not strictly guaranteed!) values that can be used as properties on objects with little fear of any collision. They’re primarily designed for special built-in behaviors of ES6 constructs, but you can also define your own symbols.

To define your own custom symbols, use the Symbol(..) native. The Symbol(..) native “constructor” is unique in that you’re not allowed to use new with it, as doing so will throw an error.

While symbols are not actually private (Object.getOwnPropertySymbols(..) reflects on the object and reveals the symbols quite publicly), using them for private or special properties is likely their primary use-case. For most developers, they may take the place of property names with _ underscore prefixes, which are almost always by convention signals to say, “hey, this is a private/special/internal property, so leave it alone!”

Native prototypes

Each of the built-in native constructors has its own .prototype object – Array.prototype, String.prototype, etc.

Prototypes As Defaults

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function isThisCool(vals,fn,rx) {
  vals = vals || Array.prototype;
  fn = fn || Function.prototype;
  rx = rx || RegExp.prototype;

  return rx.test(
    vals.map( fn ).join( "" )
  );
}

isThisCool();   // true

isThisCool(
  ["a","b","c"],
  function(v){ return v.toUpperCase(); },
  /D/
);  

Be careful if using Array.prototype as a default value. Changing this array value afterwards will also change the Array.prototype value !!!

Coercion

JavaScript coercions always result in one of the scalar primitive (see Chapter 2) values, like string, number, or boolean Note that “type coercion” is a runtime conversion for dynamically typed languages “implicit coercion” vs. “explicit coercion.”

1
2
3
4
5
var a = 42;

var b = a + "";     // implicit coercion

var c = String( a );  // explicit coercion

Abstract Value Operations

ToString

Built-in primitive values have natural stringification: null becomes “null”, undefined becomes “undefined” and true becomes “true”. numbers are generally expressed in the natural way you’d expect, but as we discussed in Chapter 2, very small or very large numbers are represented in exponent form. For regular objects, unless you specify your own, the default toString() (located in Object.prototype.toString()) will return the internal [[Class]] (see Chapter 3), like for instance “[object Object]”. But as shown earlier, if an object has its own toString() method on it, and you use that object in a string-like way, its toString() will automatically be called, and the string result of that call will be used instead. Arrays have an overridden default toString() that stringifies as the (string) concatenation of all its values (each stringified themselves), with “,” in between each value:

JSON Stringification

The JSON.stringify(..) utility will automatically omit undefined, function, and symbol values when it comes across them. If such a value is found in an array, that value is replaced by null (so that the array position information isn’t altered). If found as a property of an object, that property will simply be excluded.

1
2
3
4
5
JSON.stringify( undefined );          // undefined
JSON.stringify( function(){} );         // undefined

JSON.stringify( [1,undefined,function(){},4] ); // "[1,null,null,4]"
JSON.stringify( { a:2, b:function(){} } );    // "{"a":2}"

But if you try to JSON.stringify(..) an object with circular reference(s) in it, an error will be thrown. JSON stringification has the special behavior that if an object value has a toJSON() method defined, this method will be called first to get a value to use for serialization.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var o = { };

var a = {
  b: 42,
  c: o,
  d: function(){}
};

// create a circular reference inside `a`
o.e = a;

// would throw an error on the circular reference
// JSON.stringify( a );

// define a custom JSON value serialization
a.toJSON = function() {
  // only include the `b` property for serialization
  return { b: this.b };
};

JSON.stringify( a ); // "{"b":42}"
ToNumber
ToBoolean

First and foremost, JS has actual keywords true and false, and they behave exactly as you’d expect of boolean values. It’s a common misconception that the values 1 and 0 are identical to true/false. While that may be true in other languages, in JS the numbers are numbers and the booleans are booleans. You can coerce 1 to true (and vice versa) or 0 to false (and vice versa). But they’re not the same.

Falsy Values

All of JavaScript’s values can be divided into two categories:

Falsy Objects

A “falsy object” is a value that looks and acts like a normal object (properties, etc.), but when you coerce it to a boolean, it coerces to a false value.

Explicit Coercion

Strings <=> numbers

To coerce between strings and numbers, we use the built-in String(..) and Number(..) functions (which we referred to as “native constructors” in Chapter 3), but very importantly, we do not use the new keyword in front of them. As such, we’re not creating object wrappers.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var a = 42;
var b = String( a );

var c = "3.14";
var d = Number( c );

b; // "42"
d; // 3.14

var a = 42;
var b = a.toString();

var c = "3.14";
var d = +c; // Unary + operator, wich coerce its operand to a number

b; // "42"
d; // 3.14
Date To number

Another common usage of the unary + operator is to coerce a Date object into a number, because the result is the unix timestamp (milliseconds elapsed since 1 January 1970 00:00:00 UTC) representation of the date/time value:

~

The ~ operator first “coerces” to a 32-bit number value, and then performs a bitwise negation (flipping each bit’s parity). Consider -(x+1). What’s the only value that you can perform that operation on that will produce a 0 (or -0 technically!) result? -1. In other words, ~ used with a range of number values will produce a falsy (easily coercible to false) 0 value for the -1 input value, and any other truthy number otherwise.

Why is that relevant?

-1 is commonly called a “sentinel value,” which basically means a value that’s given an arbitrary semantic meaning within the greater set of values of its same type (numbers). The C-language uses -1 sentinel values for many functions that return >= 0 values for “success” and -1 for “failure.”

JavaScript adopted this precedent when defining the string operation indexOf(..), which searches for a substring and if found returns its zero-based index position, or -1 if not found.

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
var a = "Hello World";

// Weird tests
if (a.indexOf( "lo" ) >= 0) { // true
  // found it!
}
if (a.indexOf( "lo" ) != -1) {  // true
  // found it
}

if (a.indexOf( "ol" ) < 0) {  // true
  // not found!
}
if (a.indexOf( "ol" ) == -1) {  // true
  // not found!
}

// Nicer ones !!!
~a.indexOf( "lo" );     // -4   <-- truthy!

if (~a.indexOf( "lo" )) { // true
  // found it!
}

~a.indexOf( "ol" );     // 0    <-- falsy!
!~a.indexOf( "ol" );    // true

if (!~a.indexOf( "ol" )) {  // true
  // not found!
}

~ takes the return value of indexOf(..) and transforms it: for the “failure” -1 we get the falsy 0, and every other value is truthy.

There’s one more place ~ may show up in code you run across: some developers use the double tilde ~~ to truncate the decimal part of a number (i.e., “coerce” it to a whole number “integer”). It’s commonly (though mistakingly) said this is the same result as calling Math.floor(..).

How ~~ works is that the first ~ applies the ToInt32 “coercion” and does the bitwise flip, and then the second ~ does another bitwise flip, flipping all the bits back to the original state. The end result is just the ToInt32 “coercion” (aka truncation). Setting the Math.floor(..) difference aside, ~~x can truncate to a (32-bit) integer. But so does x | 0, and seemingly with (slightly) less effort.

Explicitly: Parsing Numeric Strings

Parsing a numeric value out of a string is tolerant of non-numeric characters – it just stops parsing left-to-right when encountered – whereas coercion is not tolerant and fails resulting in the NaN value.

1
2
3
4
5
6
7
8
var a = "42";
var b = "42px";

Number( a );  // 42
parseInt( a );  // 42

Number( b );  // NaN
parseInt( b );  // 42

If you pass a non-string, the value you pass will automatically be coerced to a string first (see “ToString” earlier), which would clearly be a kind of hidden implicit coercion. It’s a really bad idea to rely upon such a behavior in your program, so never use parseInt(..) with a non-string value.

Prior to ES5, another gotcha existed with parseInt(..), which was the source of many JS programs’ bugs. If you didn’t pass a second argument to indicate which numeric base (aka radix) to use for interpreting the numeric string contents, parseInt(..) would look at the beginning character(s) to make a guess.

Parsing Non-Strings
1
2
3
4
5
6
7
parseInt( 0.000008 );   // 0   ("0" from "0.000008")
parseInt( 0.0000008 );    // 8   ("8" from "8e-7")
parseInt( false, 16 );    // 250 ("fa" from "false")
parseInt( parseInt, 16 ); // 15  ("f" from "function..")

parseInt( "0x10" );     // 16
parseInt( "103", 2 );   // 2
Everything => Boolean

While Boolean(..) is clearly explicit, it’s not at all common or idiomatic.

Just like the unary + operator coerces a value to a number (see above), the unary ! negate operator explicitly coerces a value to a boolean. The problem is that it also flips the value from truthy to falsy or vice versa. So, the most common way JS developers explicitly coerce to boolean is to use the !! double-negate operator, because the second ! will flip the parity back to the original Can be used in a boolean context such as an if (..) .. statement

Implicit Coercion

Implicit coercion refers to type conversions that are hidden, with non-obvious side-effects that implicitly occur from other actions. In other words, implicit coercions are any type conversions that aren’t obvious (to you).

Let’s define the goal of implicit coercion as: to reduce verbosity, boilerplate, and/or unnecessary implementation detail that clutters up our code with noise that distracts from the more important intent.

Strings <–> Numbers

The + operator is overloaded to serve the purposes of both number addition and string concatenation. So how does JS know which type of operation you want to use? Consider:

1
2
3
4
5
6
7
8
var a = "42";
var b = "0";

var c = 42;
var d = 0;

a + b; // "420"
c + d; // 42

If either operand to + is a string (or becomes one with valueOf() chained with toString()), the operation will be string concatenation. Otherwise, it’s always numeric addition.

Booleans –> Numbers

Implicit coercion can really shine is in simplifying certain types of complicated boolean logic into simple numeric addition. Of course, this is not a general-purpose technique, but a specific solution for specific cases.

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
function onlyOne(a,b,c) {
  return !!((a && !b && !c) ||
    (!a && b && !c) || (!a && !b && c));
}

var a = true;
var b = false;

onlyOne( a, b, b ); // true
onlyOne( b, a, b ); // true

onlyOne( a, b, a ); // false


function betterOnlyOne() {
  var sum = 0;
  for (var i=0; i < arguments.length; i++) {
    // skip falsy values. same as treating
    // them as 0's, but avoids NaN's.
    if (arguments[i]) {
      sum += arguments[i];
    }
  }
  return sum == 1;
}

var a = true;
var b = false;

betterOnlyOne( b, a );    // true
betterOnlyOne( b, a, b, b, b ); // true

betterOnlyOne( b, b );    // false
betterOnlyOne( b, a, b, b, b, a );  // false
* –> Boolean

Sort of expression operations that require/force (implicitly) a boolean coercion:

Operators || and &&

Call them “operand selector operators.” Because they result in the value of one (and only one) of their two operands. In other words, they select one of the two operand’s values.

1
2
3
4
5
6
7
8
9
var a = 42;
var b = "abc";
var c = null;

a || b;   // 42
a && b;   // "abc"

c || b;   // "abc"
c && b;   // null

Process :

  1. Both   and && operators perform a boolean test on the first operand (a or c). If the operand is not already boolean (as it’s not, here), a normal ToBoolean coercion occurs, so that the test can be performed.
  2. For the   operator, if the test is true, the   expression results in the value of the first operand (a or c). If the test is false, the   expression results in the value of the second operand (b).
  3. Inversely, for the && operator, if the test is true, the && expression results in the value of the second operand (b). If the test is false, the && expression results in the value of the first operand (a or c).

Symbol Coercion

Explicit coercion of a symbol to a string is allowed, but implicit coercion of the same is disallowed and throws an error. Symbol values cannot coerce to number at all (throws an error either way), but strangely they can both explicitly and implicitly coerce to boolean (always true).

1
2
3
4
5
var s1 = Symbol( "cool" );
String( s1 );         // "Symbol(cool)"

var s2 = Symbol( "not cool" );
s2 + "";  

Loose Equals vs. Strict Equals

The correct description is: “== allows coercion in the equality comparison and === disallows coercion.” The implication here then is that both == and === check the types of their operands. The difference is in how they respond if the types don’t match.

Abstract equality

The == operator’s behavior is defined as “The Abstract Equality Comparison Algorithm”

  1. Basically, the first clause (11.9.3.1) says, if the two values being compared are of the same type, they are simply and naturally compared via Identity as you’d expect. For example, 42 is only equal to 42, and “abc” is only equal to “abc”. Some minor exceptions to normal expectation to be aware of:
    • NaN is never equal to itself (see Chapter 2)
    • +0 and -0 are equal to each other (see Chapter 2)
  2. The final provision in clause 11.9.3.1 is for == (but same for === !!!) loose equality comparison with objects (including functions and arrays). Two such values are only equal if they are both references to the exact same value. No coercion occurs here.
Comparing: strings to numbers
  1. If Type(x) is Number and Type(y) is String, return the result of the comparison x == ToNumber(y).
  2. If Type(x) is String and Type(y) is Number, return the result of the comparison ToNumber(x) == y.
Comparing: anything to boolean
  1. If Type(x) is Boolean, return the result of the comparison ToNumber(x) == y.
  2. If Type(y) is Boolean, return the result of the comparison x == ToNumber(y).
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
var a = "42";

// bad (will fail!):
if (a == true) {
  // ..
}

// also bad (will fail!):
if (a === true) {
  // ..
}

// good enough (works implicitly):
if (a) {
  // ..
}

// better (works explicitly):
if (!!a) {
  // ..
}

// also great (works explicitly):
if (Boolean( a )) {
  // ..
}

If you avoid ever using == true or == false (aka loose equality with booleans) in your code, you’ll never have to worry about this truthiness/falsiness mental gotcha.

Comparing: nulls to undefineds
  1. If x is null and y is undefined, return true.
  2. If x is undefined and y is null, return true.

The coercion between null and undefined is safe and predictable, and no other values can give false positives in such a check. I recommend using this coercion to allow null and undefined to be indistinguishable and thus treated as the same value.

1
2
3
4
5
6
7
8
9
10
11
12
13
// Good !!!
var a = doSomething();

if (a == null) {
  // ..
}

// Bad !!!
var a = doSomething();

if (a === undefined || a === null) {
  // ..
}
Comparing: objects to non-objects
  1. If Type(x) is either String or Number and Type(y) is Object, return the result of the comparison x == ToPrimitive(y).
  2. If Type(x) is Object and Type(y) is either String or Number, return the result of the comparison ToPrimitive(x) == y.
Edge Cases

7 “bad” cases

1
2
3
4
5
6
7
"0" == false;     // true -- UH OH!
false == 0;       // true -- UH OH!
false == "";      // true -- UH OH!
false == [];      // true -- UH OH!
"" == 0;        // true -- UH OH!
"" == [];       // true -- UH OH!
0 == [];        // true -- UH OH!
Safely Using Implicit Coercion
  1. If either side of the comparison can have true or false values, don’t ever, EVER use ==.
  2. If either side of the comparison can have [], “”, or 0 values, seriously consider not using ==.
  3. Use of typeof. typeof is always going to return you one of seven strings (see Chapter 1), and none of them are the empty “” string
Abstract Relational Comparison

The algorithm divides itself into two parts: what to do if the comparison involves both string values (second half), or anything else (first half).

  1. The algorithm first calls ToPrimitive coercion on both values, and if the return result of either call is not a string, then both values are coerced to number values using the ToNumber operation rules, and compared numerically.
1
2
3
4
5
var a = [ 42 ];
var b = [ "43" ];

a < b;  // true
b < a;  // false
  1. However, if both values are strings for the < comparison, simple lexicographic (natural alphabetic) comparison on the characters is performed:
1
2
3
4
var a = [ "42" ];
var b = [ "043" ];

a < b;  // false

a and b are not coerced to numbers, because both of them end up as strings after the ToPrimitive coercion on the two arrays. So, “42” is compared character by character to “043”, starting with the first characters “4” and “0”, respectively. Since “0” is lexicographically less than than “4”, the comparison returns false.

1
2
3
4
var a = { b: 42 };
var b = { b: 43 };

a < b;  // ??

a < b is also false, because a becomes [object Object] and b becomes [object Object], and so clearly a is not lexicographically less than b.

Other oerators

Grammar

Statements & expressions

Statements are sentences, expressions are phrases, and operators are conjunctions/punctuation.

1
2
3
var a = 3 * 6;
var b = a;
b;

In this snippet, 3 * 6 is an expression (evaluates to the value 18). But a on the second line is also an expression, as is b on the third line. The a and b expressions both evaluate to the values stored in those variables at that moment, which also happens to be 18.

Moreover, each of the three lines is a statement containing expressions. var a = 3 * 6 and var b = a are called “declaration statements” because they each declare a variable (and optionally assign a value to it). The a = 3 * 6 and b = a assignments (minus the vars) are called assignment expressions.

The third line contains just the expression b, but it’s also a statement all by itself (though not a terribly interesting one!). This is generally referred to as an “expression statement.”

Statement Completion Values
Expression Side Effects
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function vowels(str) {
  var matches;

  if (str) {
    // pull out all the vowels
    matches = str.match( /[aeiou]/g );

    if (matches) {
      return matches;
    }
  }
}

// Can be simplified as 
function vowels(str) {
  var matches;

  // pull out all the vowels
  if (str && (matches = str.match( /[aeiou]/g ))) {
    return matches;
  }
}

vowels( "Hello World" ); // ["e","o","o"]
Contextual Rules
{ .. } Curly Braces

There’s two main places (and more coming as JS evolves!) that a pair of { .. } curly braces will show up in your code. Let’s take a look at each of them.

1
2
3
4
5
6
7
8
9
var a = {
  foo: bar()
};

// What happens if we remove the var a = part of the above snippet?
{
    foo: bar()
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// `foo` labeled-loop
foo: for (var i=0; i<4; i++) {
  for (var j=0; j<4; j++) {
    if ((i * j) >= 3) {
      console.log( "stopping!", i, j );
      // break out of the `foo` labeled loop
      break foo;
    }

    console.log( i, j );
  }
}
// 0 0
// 0 1
// 0 2
// 0 3
// 1 0
// 1 1
// 1 2
// stopping! 1 3

A label can apply to a non-loop block, but only break can reference such a non-loop label. You can do a labeled break __ out of any labeled block, but you cannot continue __ a non-loop label, nor can you do a non-labeled break out of a block.

1
2
3
4
5
6
7
8
9
10
11
12
13
function foo() {
  // `bar` labeled-block
  bar: {
    console.log( "Hello" );
    break bar;
    console.log( "never runs" );
  }
  console.log( "World" );
}

foo();
// Hello
// World
1
2
3
4
5
6
7
8
9
10
11
function foo({ a, b, c }) {
  // no need for:
  // var a = obj.a, b = obj.b, c = obj.c
  console.log( a, b, c );
}

foo( {
  c: [1,2,3],
  a: 42,
  b: "foo"
} );  // 42 "foo" [1, 2, 3]
Operator Precedence

In general, operators are either left-associative or right-associative, referring to whether grouping happens from the left or from the right. It’s important to note that associativity is not the same thing as left-to-right or right-to-left processing. If hypothetically && was right-associative, it would be processed the same as if you manually used ( ) to create grouping like a && (b && c). But that still doesn’t mean that c would be processed before b. Right-associativity does not mean right-to-left evaluation, it means right-to-left grouping. Either way, regardless of the grouping/associativity, the strict ordering of evaluation will be a, then b, then c (aka left-to-right).

link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence

Precedence Operator type Associativity Individual operators
20 Grouping n/a ( … )
19 Member Access left-to-right … . …
Computed Member Access left-to-right … [ … ]
new (with argument list) n/a new … ( … )
Function Call left-to-right … ( … )
18 new (without argument list) right-to-left new …
17 Postfix Increment n/a … ++
Postfix Decrement n/a … --
16 Logical NOT right-to-left ! …
Bitwise NOT right-to-left ~ …
Unary Plus right-to-left + …
Unary Negation right-to-left - …
Prefix Increment right-to-left ++ …
Prefix Decrement right-to-left -- …
typeof right-to-left typeof …
void right-to-left void …
delete right-to-left delete …
15 Exponentiation right-to-left … ** …
14 Multiplication left-to-right … * …
Division left-to-right … / …
Remainder left-to-right … % …
13 Addition left-to-right … + …
Subtraction left-to-right … - …
12 Bitwise Left Shift left-to-right … << …
Bitwise Right Shift left-to-right … >> …
Bitwise Unsigned Right Shift left-to-right … >>> …
11 Less Than left-to-right … < …
Less Than Or Equal left-to-right … <= …
Greater Than left-to-right … > …
Greater Than Or Equal left-to-right … >= …
in left-to-right … in …
instanceof left-to-right … instanceof …
10 Equality left-to-right … == …
Inequality left-to-right … != …
Strict Equality left-to-right … === …
Strict Inequality left-to-right … !== …
9 Bitwise AND left-to-right … & …
8 Bitwise XOR left-to-right … ^ …
7 Bitwise OR left-to-right … | …
6 Logical AND left-to-right … && …
5 Logical OR left-to-right … || …
4 Conditional right-to-left … ? … : …
3 Assignment right-to-left … = …
… += …
… -= …
… **= …
… *= …
… /= …
… %= …
… <<= …
… >>= …
… >>>= …
… &= …
… ^= …
… |= …
2 yield right-to-left yield …
yield* right-to-left yield* …
1 Spread n/a ... …
0 Comma / Sequence left-to-right … , …
Short circuited

For both && and || operators, the right-hand operand will not be evaluated if the left-hand operand is sufficient to determine the outcome of the operation. Hence, the name “short circuited” (in that if possible, it will take an early shortcut out).

Automatic Semicolon Insertion (ASI)

ASI (Automatic Semicolon Insertion) is a parser-error-correction mechanism built into the JS engine.

Errors

try..finally

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function foo() {
  try {
    return 42;
  }
  finally {
    console.log( "Hello" );
  }

  console.log( "never runs" );
}

console.log( foo() );
// Hello
// 42

switch

The matching mechanism on each case expression is identical to the === algorithm. To use a coercive equality :

1
2
3
4
5
6
7
8
9
10
11
12
13
var a = "42";

switch (true) {
  case a == 10:
    console.log( "10 or '10'" );
    break;
  case a == 42:
    console.log( "42 or '42'" );
    break;
  default:
    // never gets here
}
// 42 or '42'

How to code on legacy environmnents

Async & performance

Event Loop

Until recently (ES6)? JavaScript itself has actually never had any direct notion of asynchrony built into it.

What!? That seems like a crazy claim, right? In fact, it’s quite true. The JS engine itself has never done anything more than execute a single chunk of your program at any given moment, when asked to.

It’s important to note that setTimeout(..) doesn’t put your callback on the event loop queue. What it does is set up a timer; when the timer expires, the environment places your callback into the event loop, such that some future tick will pick it up and execute it.

Parallel Threading

JavaScript never shares data across threads

Run to completion

Because of JavaScript’s single-threading, the code inside of a function is atomic. This means that once the function code starts running, the entirety of its code will finish before any of other functions code can run.

Concurrency

Event Loop Queue:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
onscroll, request 1   <--- Process 1 starts
onscroll, request 2
response 1            <--- Process 2 starts
onscroll, request 3
response 2
response 3
onscroll, request 4
onscroll, request 5
onscroll, request 6
response 4
onscroll, request 7   <--- Process 1 finishes
response 6
response 5
response 7            <--- Process 2 finishes

“Process 1” and “Process 2” run concurrently (task-level parallel), but their individual events run sequentially on the event loop queue.

Noninteracting

As two or more “processes” are interleaving their steps/events concurrently within the same program, they don’t necessarily need to interact with each other if the tasks are unrelated (no race conditions). If they don’t interact, nondeterminism is perfectly acceptable.

Interaction

More commonly, concurrent “processes” will by necessity interact, indirectly through scope and/or the DOM. When such interaction will occur, you need to coordinate these interactions to prevent “race conditions,” as described earlier.

Use concurrency interaction conditions:

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
// Condition the result assignment
function response(data) {
  if (data.url == "http://some.url.1") {
    res[0] = data;
  }
  else if (data.url == "http://some.url.2") {
    res[1] = data;
  }
}

// Condition a function call
function foo(x) {
  a = x * 2;
  if (a && b) {
    baz();
  }
}

// Condition the response function
function foo(x) {
  if (a == undefined) {
    a = x * 2;
    baz();
  }
}

Cooperation

Another expression of concurrency coordination is called “cooperative concurrency.” Here, the focus isn’t so much on interacting via value sharing in scopes (though that’s obviously still allowed!). The goal is to take a long-running “process” and break it up into steps or batches so that other concurrent “processes” have a chance to interleave their operations into the event loop queue.

For example : buffering a response to avoid single-thread process allocation & event-loop blocking

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
var res = [];

// `response(..)` receives array of results from the Ajax call
function response(data) {
  // let's just do 1000 at a time
  var chunk = data.splice( 0, 1000 );

  // add onto existing `res` array
  res = res.concat(
    // make a new transformed array with all `chunk` values doubled
    chunk.map( function(val){
      return val * 2;
    } )
  );

  // anything left to process?
  if (data.length > 0) {
    // async schedule next batch
    setTimeout( function(){
      response( data );
    }, 0 );
  }
}

// ajax(..) is some arbitrary Ajax function given by a library
ajax( "http://some.url.1", response );
ajax( "http://some.url.2", response );

Jobs

As of ES6, there’s a new concept layered on top of the event loop queue, called the “Job queue.” The most likely exposure you’ll have to it is with the asynchronous behavior of Promises. So, the best way to think about this that I’ve found is that the “Job queue” is a queue hanging off the end of every tick in the event loop queue. Certain async-implied actions that may occur during a tick of the event loop will not cause a whole new event to be added to the event loop queue, but will instead add an item (aka Job) to the end of the current tick’s Job queue. Jobs are kind of like the spirit of the setTimeout(..0) hack, but implemented in such a way as to have a much more well-defined and guaranteed ordering: later, but as soon as possible.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
console.log( "A" );

setTimeout( function(){
  console.log( "B" );
}, 0 );

// theoretical "Job API"
schedule( function(){
  console.log( "C" );

  schedule( function(){
    console.log( "D" );
  } );
} );

You might expect this to print out A B C D, but instead it would print out A C D B, because the Jobs happen at the end of the current event loop tick, and the timer fires to schedule for the next event loop tick (if available!).

Callbacks

The callback function wraps or encapsulates the continuation of the program.

1
2
3
4
5
// A
setTimeout( function(){
  // C
}, 1000 );
// B

“Do A, setup the timeout for 1,000 milliseconds, then do B, then after the timeout fires, do C.” However that description is deficient in explaining this code in a way that matches our brains to the code, and the code to the JS engine. The disconnect is both subtle and monumental, and is at the very heart of understanding the shortcomings of callbacks as async expression and management.

Nested/Chained Callbacks

Callback hell is not really related to nesting issues but rather related to code linear reading, keeping in mind that some chunks of code will be executed later. Steps sequence has to be harcoded within the callback responses. How to properly handle errors & failures?

The brittle nature of manually hardcoded callbacks (even with hardcoded error handling) is often far less graceful. Once you end up specifying (aka pre-planning) all the various eventualities/paths, the code becomes so convoluted that it’s hard to ever maintain or update it. That is what “callback hell” is all about! The nesting/indentation are basically a side show, a red herring. Furthermore, what about parallel callbacks, gates etc…

Trust issues

Callback are often used as an inversion of control (IOC) mechanism that allow to run third party code / libraries. Example of issues :

Trying to Save Callbacks

What about those issues :

Beware of zalgo functions (not predictable : synchronous or asynchronous ?) https://oren.github.io/blog/zalgo.html

1
2
3
4
5
6
7
8
9
10
11
12
// don't do ...
if (errors.length) {
    return callback(null, errors);
}

// ... instead to :
if (errors.length) {
  process.nextTick(function() {
    callback(null, errors);
  });
  return;
}

Promises

Callbacks: lack of sequentiality and lack of trustability. Promises: uninvert the inversion of control. “here’s what happens later, after the current step finishes.” Promises uses the Job queue, which is executed after the current event and before the next one in the event-loop.

Future value

Once a Promise is resolved, it stays that way forever – it becomes an immutable value at that point – and can then be observed as many times as necessary

Completion event

Return a listener object from a function, that will fire ‘completion’ or ‘failure’ events. Callback functions used as IOC pattern. Inversion of inversion of control => uninversion of control. Control is restored back to the calling code.

Uninversion of control enables a nicer separation of concerns :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var evt = foo( 42 );

evt.on( "completion", function(){
  // now we can do the next step!
} );

evt.on( "failure", function(err){
  // oops, something went wrong in `foo(..)`
} );


// let `bar(..)` listen to `foo(..)`'s completion
bar( evt );

// also, let `baz(..)` listen to `foo(..)`'s completion
baz( evt );

Promise “Events”

1
2
3
4
5
new Promise( function(resolve,reject){
    // eventually, call `resolve(..)` or `reject(..)`,
    // which are the resolution callbacks for
    // the promise.
  } );

Whenever a promise is resolved, its next step will always be the same, both now and later.

Thenable Duck Typing

It was decided that the way to recognize a Promise (or something that behaves like a Promise) would be to define something called a “thenable” as any object or function which has a then(..) method on it. It is assumed that any such value is a Promise-conforming thenable.

1
2
3
4
5
6
7
8
9
10
11
12
13
if (
  p !== null &&
  (
    typeof p === "object" ||
    typeof p === "function"
  ) &&
  typeof p.then === "function"
) {
  // assume it's a thenable!
}
else {
  // not a thenable
}

If you try to fulfill a Promise with any object/function value that happens to have a then(..) function on it, but you weren’t intending it to be treated as a Promise/thenable, you’re out of luck, because it will automatically be recognized as thenable and treated with special rules (see later in the chapter).

1
2
3
4
5
6
7
8
9
var o = { then: function(){} };

// make `v` be `[[Prototype]]`-linked to `o`
var v = Object.create( o );

v.someStuff = "cool";
v.otherStuff = "not so cool";

v.hasOwnProperty( "then" );   // false

Promise Trust

Calling Too Early

This is a concern of whether code can introduce Zalgo-like effects (see Chapter 2), where sometimes a task finishes synchronously and sometimes asynchronously, which can lead to race conditions. Promises by definition cannot be susceptible to this concern, because even an immediately fulfilled Promise (like new Promise(function(resolve){ resolve(42); })) cannot be observed synchronously (Job queue VS event queue).

Calling Too Late

Similar to the previous point, a Promise’s then(..) registered observation callbacks are automatically scheduled when either resolve(..) or reject(..) are called by the Promise creation capability. Those scheduled callbacks will predictably be fired at the next asynchronous moment.

1
2
3
4
5
6
7
8
9
10
p.then( function(){
  p.then( function(){
    console.log( "C" );
  } );
  console.log( "A" );
} );
p.then( function(){
  console.log( "B" );
} );
// A B C
Never Calling the Callback

First, nothing (not even a JS error) can prevent a Promise from notifying you of its resolution (if it’s resolved). If you register both fulfillment and rejection callbacks for a Promise, and the Promise gets resolved, one of the two callbacks will always be called.

But what if the Promise itself never gets resolved either way? Even that is a condition that Promises provide an answer for, using a higher level abstraction called a “race” (Promise.race(<iterable>)).

Calling Too Few or Too Many Times

The “too many” case is easy to explain. Promises are defined so that they can only be resolved once. If for some reason the Promise creation code tries to call resolve(..) or reject(..) multiple times, or tries to call both, the Promise will accept only the first resolution, and will silently ignore any subsequent attempts. Because a Promise can only be resolved once, any then(..) registered callbacks will only ever be called once (each).

Failing to Pass Along Any Parameters/Environment

If you don’t explicitly resolve with a value either way, the value is undefined, as is typical in JS. But whatever the value, it will always be passed to all registered (and appropriate: fulfillment or rejection) callbacks, either now or in the future.

Swallowing Any Errors/Exceptions

Cannot catch an error in the fulfilled callback IN the same then context rejected callback. It would violate the fundamental principle that Promises are immutable once resolved. Chaining a new then clause would allow us to handle that new rejected callback

Trustable Promise?

If you pass an immediate, non-Promise, non-thenable value to Promise.resolve(..), you get a promise that’s fulfilled with that value.

Trust build

Promises are a pattern that augments callbacks with trustable semantics, so that the behavior is more reason-able and more reliable. By uninverting the inversion of control of callbacks, we place the control with a trustable system (Promises) that was designed specifically to bring sanity to our async.

Chain Flow

The key to making this work is built on two behaviors intrinsic to Promises:

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
// step 1:
request( "http://some.url.1/" )

// step 2:
.then( function(response1){
  foo.bar(); // undefined, error!

  // never gets here
  return request( "http://some.url.2/?v=" + response1 );
} )

// step 3:
.then(
  function fulfilled(response2){
    // never gets here
  },
  // rejection handler to catch the error
  function rejected(err){
    console.log( err ); // `TypeError` from `foo.bar()` error
    return 42;
  }
)

// step 4:
.then( function(msg){
  console.log( msg );   // 42
} );

Returning a promise from a fulfillment or a rejected handler can delay the next step. A thrown exception inside either the fulfillment or rejection handler of a then(..) call causes the next (chained) promise to be immediately rejected with that exception.

If you call then(..) on a promise, and you only pass a fulfillment handler to it, an assumed rejection handler is substituted:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var p = new Promise( function(resolve,reject){
  reject( "Oops" );
} );

var p2 = p.then(
  function fulfilled(){
    // never gets here
  }
  // assumed rejection handler, if omitted or
  // any other non-function value passed
  // function(err) {
  //     throw err;
  // }
);

If a proper valid function is not passed as the fulfillment handler parameter to then(..), there’s also a default handler substituted:

1
2
3
4
5
6
7
8
9
10
11
12
13
var p = Promise.resolve( 42 );

p.then(
  // assumed fulfillment handler, if omitted or
  // any other non-function value passed
  // function(v) {
  //     return v;
  // }
  null,
  function rejected(err){
    // never gets here
  }
);

Let’s review briefly the intrinsic behaviors of Promises that enable chaining flow control:

While chaining flow control is helpful, it’s probably most accurate to think of it as a side benefit of how Promises compose (combine) together, rather than the main intent. As we’ve discussed in detail several times already, Promises normalize asynchrony and encapsulate time-dependent value state, and that is what lets us chain them together in this useful way.

Terminology: Resolve, Fulfill, and Reject

1
2
3
4
var p = new Promise( function(X,Y){
  // X() for fulfillment
  // Y() for rejection
} );

Promise.resolve(..) is a good, accurate name for the API method, because it can actually result in either fulfillment or rejection.

1
2
3
4
5
6
7
var rejectedTh = {
  then: function(resolved,rejected) {
    rejected( "Oops" );
  }
};

var rejectedPr = Promise.resolve( rejectedTh );

Resolve indicates a callback than can handle both fulfilled or rejected values.

Error Handling

The most natural form of error handling for most developers is the synchronous try..catch construct. Unfortunately, it’s synchronous-only, so it fails to help in async code patterns:

1
2
3
4
5
6
7
8
9
10
11
12
13
function foo() {
  setTimeout( function(){
    baz.bar();
  }, 100 );
}

try {
  foo();
  // later throws global error from `baz.bar()`
}
catch (err) {
  // never gets here
}

Solution ? Always end your chain with a final catch(..) ?

1
2
3
4
5
6
7
8
9
10
var p = Promise.resolve( 42 );

p.then(
  function fulfilled(msg){
    // numbers don't have string functions,
    // so will throw an error
    console.log( msg.toLowerCase() );
  }
)
.catch( handleErrors );

What if there is an error too within the catch(..) statement ?

Uncaught errors

Some Promise libraries have added methods for registering something like a “global unhandled rejection” handler, which would be called instead of a globally thrown error. But their solution for how to identify an error as “uncaught” is to have an arbitrary-length timer, say 3 seconds, running from time of rejection. If a Promise is rejected but no error handler is registered before the timer fires, then it’s assumed that you won’t ever be registering a handler, so it’s “uncaught.” Another more common suggestion is that Promises should have a done(..) added to them, which essentially marks the Promise chain as “done.” done(..) doesn’t create and return a Promise, so the callbacks passed to done(..) are obviously not wired up to report problems to a chained Promise that doesn’t exist.

This might sound more attractive than the never-ending chain or the arbitrary timeouts. But the biggest problem is that it’s not part of the ES6 standard, so no matter how good it sounds, at best it’s a lot longer way off from being a reliable and ubiquitous solution.

Promise Patterns

Promise.all([ .. ])

In classic programming terminology, a “gate” is a mechanism that waits on two or more parallel/concurrent tasks to complete before continuing. It doesn’t matter what order they finish in, just that all of them have to complete for the gate to open and let the flow control through. In the Promise API, we call this pattern all([ .. ]).

The main promise returned from Promise.all([ .. ]) will only be fulfilled if and when all its constituent promises are fulfilled. If any one of those promises instead is rejected, the main Promise.all([ .. ]) promise is immediately rejected, discarding all results from any other promises.

Promise.race([ .. ])

While Promise.all([ .. ]) coordinates multiple Promises concurrently and assumes all are needed for fulfillment, sometimes you only want to respond to the “first Promise to cross the finish line,” letting the other Promises fall away.

This pattern is classically called a “latch,” but in Promises it’s called a “race.” Similar to Promise.all([ .. ]), Promise.race([ .. ]) will fulfill if and when any Promise resolution is a fulfillment, and it will reject if and when any Promise resolution is a rejection.

“Finally”

Beware of resource cleaning when using promises. May need a finally-like statement cleaning objects.

Variations on all([ .. ]) and race([ .. ])

While native ES6 Promises come with built-in Promise.all([ .. ]) and Promise.race([ .. ]), there are several other commonly used patterns with variations on those semantics:

Promise limitations

Sequence Error Handling

The limitations of how Promises are designed – how they chain, specifically – creates a very easy pitfall where an error in a Promise chain can be silently ignored accidentally.

It’s basically the same limitation that exists with a try..catch that can catch an exception and simply swallow it. So this isn’t a limitation unique to Promises, but it is something we might wish to have a workaround for.

Single value

Promises by definition only have a single fulfillment value or a single rejection reason. In simple examples, this isn’t that big of a deal, but in more sophisticated scenarios, you may find this limiting.

Single Resolution
Inertia

No standard promisory wrapping. Use libaries or custom wrapper that returns a function producing a new Promise for each .

Promise Uncancelable

Once you create a Promise and register a fulfillment and/or rejection handler for it, there’s nothing external you can do to stop that progression if something else happens to make that task moot. This cancel feature violates the future-value’s trustability (external immutability), but moreover is the embodiment of the “action at a distance” anti-pattern.

Promise Performance

Comparing how many pieces are moving with a basic callback-based async task chain versus a Promise chain, it’s clear Promises have a fair bit more going on, which means they are naturally at least a tiny bit slower. Think back to just the simple list of trust guarantees that Promises offer, as compared to the ad hoc solution code you’d have to layer on top of callbacks to achieve the same protections.

Generators

Breaking Run-to-Completion

As bizarre as it may seem, ES6 introduces a new type of function that does not behave with the run-to-completion behavior. This new type of function is called a “generator.”

Input and Output
Iteration messaging
1
2
3
4
5
6
7
8
9
10
11
12
function *foo(x) {
  var y = x * (yield "Hello");  // <-- yield a value!
  return y;
}

var it = foo( 6 );

var res = it.next();  // first `next()`, don't pass anything
res.value;        // "Hello"

res = it.next( 7 );   // pass `7` to waiting `yield`
res.value;    // 42 
Multiple iterators

each time you construct an iterator, you are implicitly constructing an instance of the generator which that iterator will control.

Iterators & iterables

An object that has the next() method on its interface is called an iterator. But a closely related term is iterable, which is an object that contains an iterator that can iterate over its values. As of ES6, the way to retrieve an iterator from an iterable is that the iterable must have a function on it, with the name being the special ES6 symbol value Symbol.iterator

Holy grail ! Handle asynchronous requests through a synchronous flow

We have totally synchronous-looking code inside the generator (other than the yield keyword itself), but hidden behind the scenes, inside of foo(..), the operations can complete asynchronously.

That’s huge! That’s a nearly perfect solution to our previously stated problem with callbacks not being able to express asynchrony in a sequential, synchronous fashion that our brains can relate to.

In essence, we are abstracting the asynchrony away as an implementation detail, so that we can reason synchronously/sequentially about our flow control: “Make an Ajax request, and when it finishes print out the response.” And of course, we just expressed two steps in the flow control, but this same capability extends without bounds, to let us express however many steps we need to.

Synchronous Error Handling

The yield-pause nature of generators means that not only do we get synchronous-looking return values from async function calls, but we can also synchronously catch errors from those async function calls!

Generators + Promises

The best of all worlds in ES6 is to combine generators (synchronous-looking async code) with Promises (trustable and composable). But what should the iterator do with the promise? It should listen for the promise to resolve (fulfillment or rejection), and then either resume the generator with the fulfillment message or throw an error into the generator with the rejection reason. The natural way to get the most out of Promises and generators is to yield a Promise, and wire that Promise to control the generator’s iterator.

Promises, Hidden

As a word of stylistic caution, be careful about how much Promise logic you include inside your generators. The whole point of using generators for asynchrony in the way we’ve described is to create simple, sequential, sync-looking code, and to hide as much of the details of asynchrony away from that code as possible. We treat asynchrony, and indeed Promises, as an implementation detail.

Generator Delegation

yield * delegates/transfers the iterator instance control to the next generator. yield *[1,2,3] also works. The purpose of yield-delegation is mostly code organization

Delegating messages

Message are delegated both ways.

Error delegations

Exceptions are also delegated.

TL;DR

the generator can be paused in mid-completion (entirely preserving its state), and it can later be resumed from where it left off.

This pause/resume interchange is cooperative rather than preemptive, which means that the generator has the sole capability to pause itself, using the yield keyword, and yet the iterator that controls the generator has the sole capability (via next(..)) to resume the generator.

The yield / next(..) duality is not just a control mechanism, it’s actually a two-way message passing mechanism. A yield .. expression essentially pauses waiting for a value, and the next next(..) call passes a value (or implicit undefined) back to that paused yield expression.

The key benefit of generators related to async flow control is that the code inside a generator expresses a sequence of steps for the task in a naturally sync/sequential fashion. The trick is that we essentially hide potential asynchrony behind the yield keyword – moving the asynchrony to the code where the generator’s iterator is controlled.

In other words, generators preserve a sequential, synchronous, blocking code pattern for async code, which lets our brains reason about the code much more naturally, addressing one of the two key drawbacks of callback-based async.

performance

Asynchrony objective : performance.

Web workers

Run processing intensive tasks on secondary process thread to prevent blocking the main process thread. From JS program, invoke a worker with

1
var w1 = new Worker( "http://some.url.1/mycoolworker.js" );

Workers do not share any scope or resources with each other or the main program – that would bring all the nightmares of threaded programming to the forefront – but instead have a basic event messaging mechanism connecting them.

Here’s how to listen for events (actually, the fixed “message” event):

1
2
3
w1.addEventListener( "message", function(evt){
    // evt.data
} );

And you can send the “message” event to the Worker:

1
w1.postMessage( "something cool to say" );

Inside the Worker, the messaging is totally symmetrical:

1
2
3
4
5
6
7
// "mycoolworker.js"

addEventListener( "message", function(evt){
    // evt.data
} );

postMessage( "a really cool reply" );

To kill a Worker immediately from the program that created it, call terminate() on the Worker object (like w1 in the previous snippets). Abruptly terminating a Worker thread does not give it any chance to finish up its work or clean up any resources. It’s akin to you closing a browser tab to kill a page.

Worker Environment

Inside the Worker, you do not have access to any of the main program’s resources. That means you cannot access any of its global variables, nor can you access the page’s DOM or other resources. Remember: it’s a totally separate thread.

You can, however, perform network operations (Ajax, WebSockets) and set timers. Also, the Worker has access to its own copy of several important global variables/features, including navigator, location, JSON, and applicationCache.

You can also load extra JS scripts into your Worker, using importScripts(..):

1
2
// inside the Worker
importScripts( "foo.js", "bar.js" );
Data transfer

If you pass an object, a so-called “Structured Cloning Algorithm” (https://developer.mozilla.org/en-US/docs/Web/Guide/API/DOM/The_structured_clone_algorithm) is used to copy/duplicate the object on the other side. This algorithm is fairly sophisticated and can even handle duplicating objects with circular references. The to-string/from-string performance penalty is not paid, but we still have duplication of memory using this approach. There is support for this in IE10 and above, as well as all the other major browsers.

An even better option, especially for larger data sets, is “Transferable Objects” (http://updates.html5rocks.com/2011/12/Transferable-Objects-Lightning-Fast). What happens is that the object’s “ownership” is transferred, but the data itself is not moved. Once you transfer away an object to a Worker, it’s empty or inaccessible in the originating location – that eliminates the hazards of threaded programming over a shared scope. Of course, transfer of ownership can go in both directions.

There really isn’t much you need to do to opt into a Transferable Object; any data structure that implements the Transferable interface (https://developer.mozilla.org/en-US/docs/Web/API/Transferable) will automatically be transferred this way (support Firefox & Chrome).

For example, typed arrays like Uint8Array (see the ES6 & Beyond title of this series) are “Transferables.” This is how you’d send a Transferable Object using postMessage(..):

1
2
3
// `foo` is a `Uint8Array` for instance

postMessage( foo.buffer, [ foo.buffer ] );

The first parameter is the raw buffer and the second parameter is a list of what to transfer.

Browsers that don’t support Transferable Objects simply degrade to structured cloning, which means performance reduction rather than outright feature breakage.

Review

Web Workers let you run a JS file (aka program) in a separate thread using async events to message between the threads. They’re wonderful for offloading long-running or resource-intensive tasks to a different thread, leaving the main UI thread more responsive.

SIMD proposes to map CPU-level parallel math operations to JavaScript APIs for high-performance data-parallel operations, like number processing on large data sets.

Finally, asm.js describes a small subset of JavaScript that avoids the hard-to-optimize parts of JS (like garbage collection and coercion) and lets the JS engine recognize and run such code through aggressive optimizations. asm.js could be hand authored, but that’s extremely tedious and error prone, akin to hand authoring assembly language (hence the name). Instead, the main intent is that asm.js would be a good target for cross-compilation from other highly optimized program languages – for example, Emscripten (https://github.com/kripken/emscripten/wiki) transpiling C/C++ to JavaScript.

Benchmark & performance

Measuring execution time with (new Date()).getTime(); is not a solution.

Repetition

Do not use single samples, repeat samples for a given length of time. The length of time to repeat across should be based on the accuracy of the timer you’re using, specifically to minimize the chances of inaccuracy. The less precise your timer, the longer you need to run to make sure you’ve minimized the error percentage.

Benchmark.js

JS performance test in a specific environment

jsPerf.com

It uses the Benchmark.js library we talked about earlier to run statistically accurate and reliable tests, and makes the test on an openly available URL that you can pass around to others.

Each time a test is run, the results are collected and persisted with the test, and the cumulative test results are graphed on the page for anyone to see.

Graphic logo from github.com/voodootikigod/logo.js/