Chapter 1, Accustoming Yourself to JavaScript

Item 1: Know Which JavaScript You Are Using

ES5 introduced strict mode

// at the very beginning of the program 
"use strict";
// or at the beginning of function body
function f(x) {
    "use strict";
    // ...
}

"use strict" directive is only recognized at the top of a script or function. solution is to wrap your code in functions so strict mode could always be applied.

(function(){ "use strict"; function f(){ // ... } })();

Item 2: Understand JavaScript's Floating-Point Numbers

all numbers in javascript are double-precision floating-point numbers - commonly known as "doubles".

most arithmetic operators work with integers, real numbers, or a combination of the two:

21 - 12.3; // 8.7

the bitwise arithmetic operators, however are special. they implicitly convert them to 32-bit integers.

8 | 1; // 9

(8).toString(2); // "1000", the result drops extra 0 bits on the left since they don't affect the value.

parseInt("1001", 2); // 9

while 64 bits of precision is reasonably large, doubles can still only represent a finite set of numbers, rather than the infinite set of real numbers. floating-point arithmetic can only product approximate results.

(0.1 + 0.2) + 0.3; // 0.6000000000000001 0.1 + (0.2 + 0.3); // 0.6

one workaround is to work with integer values wherever possible.

(10 + 20) + 30; // 60 10 + (20 + 30); // 60

Item 3: Beware of Implicit Coercions

in javascript, you can do this

3 + true; // 4

javascript coerces a value to the expected type by various automatic conversion protocols.

arithmetic operators -, *, / and % all attempt to convert their arguments to numbers before doing calculation.

the operator + is subtler:

"2" + 3; // "23" 1 + "2" + 3; // "123"

bitwise operators not only convert to numbers but to the subset of numbers that can be represented as 32-bit integers.

a null will not fall in an arithmeticcalculation, but silently convert to 0

an undefined will convert to NaN, and javascript follows IEEE floating-point standard that NaN be treated as unequal to itself.

var x = NaN; x === NaN; // false

the isNaN() function is not very reliable because it comes with its own implicit coercion, converting its argument to a number before testing the value.

isNaN(NaN); // true

// these are not NaN isNaN("foo"); // true isNaN(undefined); // true isNaN({}); // true isNaN({valueOf: "foo"}); // true

there is an idiom that is both reliable and concise for testing for NaN. since NaN is the only javascript value that is treated as unequal to itself, we can do:

var a = NaN; a !== a; // true

var b = "foo"; b !== b; // false

var c = undefined; c !== c; // false

var d = {}; d !== d; // false

to abstract the pattern:

function isReallyNaN(x) { return x !== x; }

Objects can also be coerced to primitives by calling toString() and valueOf() (convert string and number)

if an object contains both toString and valueOf, javascript will blindly choosing valueOf over toString.

var obj = { toString: function() { return "[object MyObject]"; }, valueOf: function() { return 17; } };

"object: " + obj; // "object: 17"

in general, coercion to strings if far more common and useful than to numbers. it's best to avoid valueOf unless your object really is a numberic abstraction and obj.toString() produces a string representation of obj.valueOf().

last kind of coercion is truthiness.(operators such as if, ||, and &&).

there are 7 falsy values: false, 0, -0, "", NaN, null, and undefined. all other values are truthy.

Item 4: Prefer Primitives to Object Wrappers

in addition to obejcts, javascript has 5 types of primitive values: booleans, numbers, strings, null, and undefined. (typeof reports the type of null as "object", but ES5 fixes it)

var s = new String("hello");

typeof s; // "object" typeof "hello"; // "string"

this is an important difference

var s1 = new String("hello"); var s2 = new String("hello"); s1 === s2; // false s1 == s2; // false; even nonstrict comparison is false

since these wrappers don't behave quite right, they don't serve much of a purpose.

"hello".someProperty = 17; "hello".someProperty; // undefined

since the implicit wrapping produces a new String object each time it occurs, the update to the first wrapper object has no lasting effect.

if you set properties on what you expect to be an object, but use a primitive value by mistake, your program will simply silently ignore the update and continue.

Item 5: Avoid using == with Mixed Types

"1.0e0" == { valueOf: function(){ return true; } }; // true, both parses as number 1

it's easy to convert value to numbers explicitly using +:

var today = new Date();
if (+form.month.value == (today.getMonth() + 1)) {
    // ... 
}

when the two arguments are of the same type, there is no difference between == and ===. but using strict equality is a good way to make it clear to readers that there is no conversion involved in the comparison.

Item 6: Learn the Limits of Semicolon Insertion

the first rule semicolon insertion is:

Semicolons are only ever inserted before a } token, after one or more new lines, or at the end of the program input.

which means you can only leave out semicolons at the end of a line, block, or program.

the second rule is:

Semicolons are only ever inserted when the next input token cannot be parsed.

means semicolon insertion is an error correction mechanism. but you always have to pay attention to the start of the next statement to detect whether you can legally omit a semicolon.

if the next line starts with any of (, [, +, -, and /, no semicolon will be inserted. (or you can add semicolon at the beginning of the line)

(function() {
    // ...
})()
(function() {
    // ...
})()
// will become
(function() {
    // ...
})()(function() {
    // ...
})();
// add semicolon before to avoid
;(function() {
    // ...
})()
;(function() {
    // ...
})()

and never put new lines before return, throw, break, continue, ++, or , semicolons will be inserted and may with no errors.

a
++
b
// will be parsed as
a;
++b;

the third and final rule:

Semicolons are never inserted as separators in the head of a for loop or as empty statements.

function infiniteloop() { while (true) } // parse error
function infiniteloop() { while (true); } // semicolon is needed

Item 7: Think of Strings As Sequences of 16-Bit Code Units

The basic of Unicode are simple: Every unit of text of all the world's writting system is assigned a unique integer between 0 and 1,114,111, known as a code point in Unicode terminology.

code unit is a simple, one-to-one mapping between code points and the elements of the encoding. UCS-2 the original 16-bit encoding, was made up of individual 16-bit code units, each of which corresponded to a single Unicode code point.

later the standard expanded to 220 code points(17 subranges of 216 code points), Basic Multilingual Plane (BMP) consists of the original 216 code points, the addtional 16 ranges are known as the supplementary planes.

UTF-16, is mostly the same, but with the additional of what are known as surrogate pairs: pairs of 16-bit code units that together encode a single code point 216 or greater.

UTF-16 is a variable-length encoding: each code poing in a UTF-16 encoding may require either one or two 16-byte code units. finding the nth code point of a string is no longer a constant-time operation: it generally requires searching from the beginning of the string.

But by the time Unicode expanded in size, JavaScript had already committed to 16-bit string elements. String methods like length, charAt, and chatCodeAt all work at the level of code units rather than code points

An element of a JavaScript string is a 16-bit code unit.

Unicode code points 216 and above are represented in JavaScript by two code units, known as a surrogate pair. surrogate pairs throw off string element counts, affecting length, charAt, charCodeAt, and regular expression patterns such as .

Chapter 2, Variable Scope

Item 8: Minimize Use of the Global Object

you have two mechanisms to create a global variable:

var foo = "foo";
// or
this.foo = "foo";

the var declaration has the benefit of more clearly conveying the effect on the program's scope.

Item 9: Always Declare Local Variables

unintentional global variable:

function swap(a, i, j) {
    temp = a[i]; // global
    a[i] = a[j];
    a[j] = temp;
}
// always declare with var
 var temp = a[i]; // local

use lint tools to help check for unbound variables.

Item 10: Avoid with

function f(x, y) {
    with (Math) {
        return min(round(x), sqrt(y)); // ambiguous references
    }
}

nothing in the syntax distinguishes two types of variables (Math's and function local's). JavaScript looks variables up in with block first, then outer scope. you can not ensure variables don't already exists in the inner scope:

Math.x = 0;
Math.y = 0;
f(2, 9); // 0

the best alternative is simply bind with short variable name:

function f(x, y) {
    var min = Math.min, round = Math.round, sqrt = Math.sqrt;
    return min(round(x), sqrt(y));
}
Math.x = 0;
Math.y = 0;
f(2, 9); // 2

Item 11: Get Comfortable with Closures

The first fact is that JavaScript allows you to refer to variables that were defined outside of the current function.

function makeSandwich() {
    var magicIngredient = "peanut butter";
    function make(filling) {
        return magicIngredient + " and " + filling;
    }
    return make("jelly");
}
makeSandwich(); // "peanut butter and jelly"

The second fact is that functions can refer to variables defined in outer functions even after those outer functions have returned.

var f = sandwi 
function sandwichMaker() {
    var magicIngredient = "peanut butter";
    function make(filling) {
        return magicIngredient + " and " + filling;
    }
    return make;
}
var f = sandwichMaker();
f("jelly"); // "peanut butter and jelly"
f("bananas"); // "peanut butter and bananas"

JavaScript function values contain more information than just the code required to execute when they're called. They also internally store any variables they may refer to that are defined in their enclosing scopes. Functions that keep track of variables from their containing scopes are known as closures.

The third and final fact to learn about closures is that they can update the values of outer variables. Closures actually store references to their outer variable.

function box() {
    var val = undefined;
    return {
        set: function(newVal) { val = newVal; },
        get: function() { return val; },
        type: function() { return typeof val; }
    };
}
var b = box();
b.type(); // "undefined"
b.set(98.6);
b.get(); // 98.6
b.type(); // "number"

three closures share the same val variable. (by reference)

Item 12: Understand Variable Hoisting

JavaScript supports lexical scoping: with only a few exceptions, A reference to a variable foo is bound to the nearest scope in which foo was declared. However, JavaScript does not support block scoping: Variable definitions are not scoped to their nearest enclosing statement or block, but rather to their containing function.

JavaScript implicitly hoists the declaration part to the top of the enclosing function and leaves the assignment in place.

function f() {
    // ...
    {
        // ...
        var x = /* ... */
        // ...
    }
    // ...
}

is actually

function f() {
    var x;
    // ...
    {
        // ...
        x = /* ... */
        // ...
    }
    // ...
}

the one exception to JavaScript's lack of block scoping is the try..catch, it binds a caught exception to a variable that is scoped just to the catch block.

Item 13: Use Immediately Invoked Function Expressions to Create Local Scopes

Closures store their outer variables by reference, not by value.

function wrapElement(a) {
    var result = [];
    for (var i = 0, n = a.length; i < n; i++) {
        result[i] = function() { return a[i]; };
    }
    return result;
}
var wrapped = wrapElement([10, 20, 30, 40, 50]);
var f = wrapped[0];
f(); // undefined

Immediately invoked function expression (IIFE, pronounced "iffy"), is an indispensable workaround for JavaScript's lack of block scoping.

function wrapElement(a) {
    var result = [];
    for (var i = 0, n = a.length; i < n; i++) {
        (function() { // new scope
            var j = i; 
            result[i] = function() { return a[j]; };
        })();
    }
    return result;
}
// or bind the local variable as a parameter to the IIFE and pass its value as an argument:
(function(j) { // new scope
    result[i] = function() { return a[j]; };
})(i);

however, wrapping a block in a function can introduce some subtle changes to the block. first, the block cannot contain any break or continue statements that jump outside of the block, since it's illegal to break or continue outside of a function.

second, if the block refers to this or the special arguments varaible, the IIFE changes their meanings. (chapter 3 discusses techniques for working with this and arguments)

Item 14: Beware of Unportable Scoping of Named Function Expressions

function double(x) { return x * 2; }

depending where it appears, this could be either a function declaration or a named function expression.

var f = function double(x) { return x * 2; };

this binds the function to f rather than double. named function expressions is not useful except for debugging.

named function expressions also have a notorious source of scoping and compatibility issue.

var constructor = function() { return null; };
var f = function f() {
    return constructor();
};
f(); // {} (in ES3 environments)

it should produce null, but the named function expression inherits Object.prototype.constructor in its scope, it produces a new object instead (ES3 environments)

ES5 corrected it, but some environments still have unexpected result for even anonymous function expressions:

var constructor = function() { return null; };
var f = function() {
    return constructor();
};
f(); // {} (in nonconformant environments)

it should produce null, but the named function expression inherits Object.prototype.constructor in its scope, it produces a new object instead (ES3 environments)

Item 15: Beware of Unportable Scoping of Block-Local Function Declarations

always keep function declaration at the outermost level of a program or a containing function to avoid unportable behavior.

use var declarations with conditional assignment instead of conditional function declarations.

Item 16: Avoid Creating Local Variables with eval

Item 17: Prefer Indirect eval to Direct eval

(i don't use eval)

Chapter 3, Working with Functions

Item 18: Understand the Difference between Function, Method, and Constructor Calls

in JavaScript, functions, methods, and class constructors are three different usage patterns of one single construct: functions.

as normal function

function hello(name) {
    return "hello, " + name;
}
hello("foo"); // "hello, foo"

as method

var obj = {
    hello: function() {
        return "hello, " + this.name;
    },
    name: "foo"
};
obj.hello(); // "hello, foo"
var obj2 = {
    hello: obj.hello,
    name: "bar"
};
obj2.hello(); // "hello, bar"

the method call expression itself determines the binding of this, also known as the call's receiver.

obj.hello() looks up the hello property of obj and calls it with receiver obj.

obj2.hello() looks up the hello property of obj2, which to be the same function as obj.hello, but calls it with receiver obj2.

in general, calling a method on an object looks up the method and then uses the object as the method's receiver.

ES5 strict mode changes the default binding of this to undefined:

function hello() {
    "use strict";
    return "hello" + this.name;
}
hello(); // error

as constructor

function User(name, passwordHash) {
    this.name = name;
    this.passwordHash = passwordHash;
}
var u = new User("me", "xxx");
u.name; // "me"

unlike function calls and method calls, a constructor call (with new) passes a brand-new object as the value of this, and implicitly returns the new object as its result (receiver). the constructor function's primary role is to initialize the object.

Item 19: Get Comfortable Using Higher-Order Functions

(no notes)

Item 20: Use call to Call Methods with a Custom Receiver

f.call(obj, arg1, arg2, arg3);
// behaves similarly to calling it directly:
f(arg1, arg2, arg3);

you can call method even it is not stored anywhere in the object:

dict.hasOwnProperty = 1;
dict.hasOwnProperty("foo"); // error, 1 is not a function
var hasOwnProperty = {}.hasOwnProperty;
dict.foo = 1;
delete dict.hasOwnProperty; // dict has no hasOwnProperty property now
hasOwnProperty.call(dict, "foo"); // true
hasOwnProperty.call(dict, "hasOwnProperty"); // false
  • use call method to call a function with a custom receiver
  • use call method for calling methods that may not exist on a given object
  • use call method for defining higher-order functions that allow clients to provide a receiver for the callback.

Item 21: Use apply to Call Functions with Different Numbers of Arguments

variadic or variable-arity function: a function can take any number of arguments.

var scores = getAllScores();
average.apply(null, scores); // pass null since the function does not refer to this

use the first argument of apply to provide a receiver for variadic methods.

Item 22: Use arguments to Create Variadic Functions

JavaScript provides every function with an implicit local variable called arguments: it contains indexed properties for each actual argument and a length property indicating how many arguments where provided.

A good rule of thumb is that whenever you provide a variable-arity function for convenience, you should also provide a fixed-arity version that takes an explicit array.

function average() { // takes any number of arguments
    return averageOfArray(arguments); // takes an array argument
}

Item 23: Never Modify the arguments Object

the arguments object is not a copy of the function's arguments. all named arguments are aliases to their corresponding indices in the arguments object. (don't try to shift() the arguments object, it won't change the aliases)

however, in ES5 strict mode, function parameters do not alias their arguments object.

function strict(x) {
    "use strict";
    arguments[0] = "modified";
    return x === arguments[0];
}
function nonstrict(x){
    arguments[0] = "modified";
    return x === arguments[0];
}
strict("unmodified"); // false
nonstrict("unmodified"); // true

copy the arguments to a real array before modifying it:

function callMethod(obj, method) {
    var args = [].slice.call(arguments, 2); // shift 2, a true Array instance
    return object[method].apply(obj, args);
}

Item 24: Use a Variable to Save a Reference to arguments

if you use arguments when making an iterator, bind a local variable to the arguments object. since a new arguments variable is implicitly bound in the body of each function.

function values() {
    var i = 0, n = arguments.length, a = arguments;
    return {
        hasNext: function() {
            return i < n;
        },
        next: function() {
            if (i >= n) {
                throw new Error("end of iteration");
            }
            return a[i++]; // if use arguments[i++] here, it won't work, since the function has a new arguments object each time it is called
        }
    };
}

Item 25: Use bind to Extract Methods with a Fixed Receiver

var buffer = {
    entries: [],
    add: function(s) {
        this.entries.push(s);
    },
    join: function() {
        return this.entries.join("");
    }
};
var source = ["867", "-", "5309"];
source.forEach(buffer.add); // error, entries is undefined

here, the receiver of buffer.add is not buffer, a function's receiver is determined by how it is called. when we pass it to forEach, we don't know the implementation (forEach uses the global object as the default receiver)

// we can fix it by pass an extra argument as receiver
source.forEach(buffer.add, buffer);

but not every higher-order functions offer their clients the courtesy of providing a receiver for their callbacks.

// we can call buffer.add explicitly
source.forEach(function(s) { 
    buffer.add(s);
});

ES5 added bind for the pattern

// use bind
source.forEach(buffer.add.bind(buffer));

keep in mind that buffer.add.bind(buffer) creates a new function rather than modifying the buffer.add function:

buffer.add == buffer.add.bind(buffer); // false

this is a subtle but crucial point: bind is safe to call even on a function that may be shared by other parts of a program.

Item 26: Use bind to Curry Functions

function simpleURL(protocol, domain, path) {
    return protocol + "://" + domain + "/" + path;
}
var urls = paths.map(function(path) {
    return simpleURL("http", siteDomain, path);
})

here, protocol and domain are fixed for each iteration, only the third argument is needed. we can use bind on simpleURL to construct it:

var urls = paths.map(simpleURL.bind(null, "http", siteDomain));

the call to simpleURL.bind produces a new function that delegates to simpleURL.

Item 27: Prefer Closures to Strings for Encapsulating Code

perfer APIs that accept functions to call rather than strings to eval.

Item 28: Avoid Relying on the toString Method of Functions

(no notes)

Item 29: Avoid Nonstandard Stack Inspection Properties

avoid the nonstandard arguments.caller and arguments.callee`

Chapter 4, Objects and Prototypes

Item 30: Understand the Difference between prototype, getPrototypeOf, and __proto__

Prototypes involve 3 separate but related accessors:

  • C.prototype is used to establish the prototype of objects created by new C()
  • Object.getPrototypeOf(obj) is the standard ES5 mechanism for retrieving obj's prototype object.
  • obj.__proto__ is a nonstandard mechanism for retrieving obj's prototype object.

A class is a design pattern consisting of a constructor function and an associated prototype.

Item 31: Prefer Object.getPrototypeOf to __proto__

for JavaScript environments that do not provide the ES5 API

if (typeof Object.getPrototypeOf === "undefined") {
    Object.getPrototypeOf = function(obj) {
        var t = typeof obj;
        if (!obj || (t !== "object" && t !== "function")) {
            throw new TypeError("not an object);
        }
        return obj.__proto__;
    };
}

Item 32: Never Modify __proto__

use Object.create to provide a custom prototype for new objects.

Item 33: Make Your Constructors new-Agnostic

ES5 introduces Object.create

function User(name, passwordHash) {
    var self = this instanceof User
                ? this
                : Object.create(User.prototype);
    self.name = name;
    self.passwordHash = passwordHash;
    return self;
}

for old environments

if (typeof Object.create === "undefined") {
    Object.create = function(prototype) {
        function C() { }
        C.prototype = prototype;
        return new C();
    };
}

Item 34: Store Methods on Prototypes

storing methods on instance objects creates multiple copies of the functions, one per instance object.

perfer storing methods on prototypes over storing them on instance objects.

Item 35: Use Closures to Store Private Data

(same as the pattern book)

Item 36: Store Instance State Only on Instance Objects

mutable data can be problematic when shared, and prototypes are shared between all their instances.

store mutable per-instance state on instance objects.

Item 37: Recognize the Implicit Binding of this

function CSVReader(separators) {
    this.separators = separators || [","];
    this.regexp = new RegExp(this.separators.map(function(sep) {
        return "\\" + sep[0];
    }).join("|"));
}
CSVReader.prototype.read = function(str) {
    var lines = str.trim().split(/\n/);
    return lines.map(function(line) {
        return line.split(this.regexp); // wrong this
    });
};

map binds its callback's receiver to lines array.

CSVReader.prototype.read = function(str) {
    var lines = str.trim().split(/\n/);
    var self = this; // save a reference to outer this-binding
    return lines.map(function(line) {
        return line.split(self.regexp); // use outer this
    });
};

ES5 can use bind

CSVReader.prototype.read = function(str) {
    var lines = str.trim().split(/\n/);
    return lines.map(function(line) {
        return line.split(this.regexp);
    }.bind(this)); // bind to outer this-binding
};

Item 38: Call Superclass Constructors from Subclass Constructors

(this chapter contains simple objects for game: Scene, Actor, check gitst)

Call the superclass constructor explicitly from subclass constructors, passing this as the explicit receiver.

function SpaceShip(scene, x, y) {
    Actor.call(this, scene, x, y);
    this.points = 0;
}

Use Object.create to construct the subclass prototype object to avoid calling the superclass constructor.

SpaceShip.prototype = Object.create(Actor.prototype);

Item 39: Never Reuse Superclass Property Names

(no notes)

Item 40: Avoid Inheriting from Standard Classes

Inheriting from standard classes tends to break due to special internal properties such as [[Class]] (check book).

Perfer delegating to properties instead of inheriting from standard classes.

Item 41: Treat Prototypes As an Implementation Detail

Objects are interfaces; prototypes are implementations.

Avoid inspecting the prototype structure of objects you don't control.

Avoid inspecting properties that implement the internals of objects you don't control.

Item 42: Avoid Reckless Monkey-Patching

(no notes)

Chapter 5, Arrays and Dictionaries

Item 43: Build Lightweight Dictionaries from Direct Instances of Object

use object literals to construct lightweight dictionaries

lightweight dictionaries should be direct descendants of Object.prototype to protect against prototype pollution in for..in loops

Item 44: Use null Prototypes to Prevent Prototype Pollution

ES5 offers the first standard way to create an object with no prototype.

var x = Object.create(null);
Object.getPrototypeOf(x) === null; // true

for old environments

var x = { __proto__: null };
x instanceof Object; // false (non-standard)

Item 45: Use hasOwnProperty to Protect Against Prototype Pollution

(no notes)

Item 46: Prefer Arrays to Dictionaries for Ordered Collections

avoid relying on the order in which for...in loops enumerate object properties.

if you aggregate data in a dictionary, make sure the aggregate operations are order-insensitive.

use arrays instead of dictionary objects for ordered collections.

Item 47: Never Add Enumerable Properties to Object.prototype

consider writing a function instead of an Object.prototype method.

if you do add properties to Object.prototype, use ES5's Object.defineProperty to define them as nonenumerable properties.

Item 48: Avoid Modifying an Object during Enumeration

make sure not to modify any object while enumerating its properties with a for...in loop.

use a while or for loop instead of a for...in loop when iterating over an object whose contents might change during the loop.

Item 49: Prefer for Loops to for...in Loops for Array Iteration

(no notes)

Item 50: Prefer Iteration Methods to Loops

ES5 provides Array.prototype.forEach

for (var i = 0, n = players.length; i < n; i++) {
    players[i].score++;
}

can be written as

players.forEach(function(p){
    p.score++;
});

also provides Array.prototype.map

var timmed = input.map(function(s) {
    return s.trim();
});

Array.prototype.filter:

listings.filter(function(listing) {
    return listing.price >= min && listing.price <= max;
});

Array.prototype.some and Array.prototype.every:

[1, 10, 100].some(function(x) { return x > 5; }); // true
[1, 10, 100].some(function(x) { return x < 0; }); // false
[1, 2, 3, 4, 5].every(function(x) { return x > 0; }); // true
[1, 2, 3, 4, 5].every(function(x) { return x < 3; }); // false

**Item 51: Reuse Generic Array Methods on Array-Like Objects***

Any object can be used with generic Array methods if it has indexed properties and an appropriate length property.

Item 52: Prefer Array Literals to the Array Constructor

The Array constructor behaves differently if its first argument is a Numbers: [17] and Array[17] do completely different things. Array[17] create an array with no elements but the length property is 17.

Chapter 6, Library and API Design

Item 53: Maintain Consistent Conventions

argument order, always keep the same order.

use consistent convertions for variable names and function signatures.

Item 54: Treat undefined As "No Value"

  • avoid using undefined to represent anything other than the absence of a specific value.
  • use descriptive string values or objects with named boolean properties, rather than undefined or null, to represent application-specific flags.
  • Test for undefined instead of checking arguments.length to provide parameter default values.
  • Never use truthiness tests for parameter default values that should allow 0, NaN, or the empty string as valid arguments.

Item 55: Accept Options Objects for Keyword Arguments

  • use options object to make APIs more readable and memorable.
  • the arguments provided by an options object should all be treated as optional.
  • use an extend utility function to abstract out the logic of extracting values from options object.
function extend(target, source) {
    if (source) {
        for (var key in source) {
            var val = source[key];
            if (typeof val !== "undefined") {
                target[key] = val;
            }
        }
    }
    return target;
}
// usage
function Alert(parent, message, opts) {
    opts = extend({
        width: 320,
        height: 240
    });
    opts = extend({
        title: "Alert",
        // ...
    }, opts);
    extend(this, opts);
}

Item 56: Avoid Unnecessary State

prefer stateless APIs where possible

c.fillStyle = "blue";
c.fillText("text", 0, 30);
// this is better, and stateless
c.fillText("text", 0, 30, { fillStyle: "blue" });

Item 57: Use Structural Typing for Flexible Interfaces

structural typing or duck typing: any object will do so long as it has the expected structure (if it looks like a duck, swims like a duck, and quacks like a duck...).

avoid inheritance when structural interface are more flexible and lightweight.

Item 58: Distinguish between Array and Array-Like

APIs should never overload structural types with other overlapping types.

accept true arrays instead of array-like objects when overloading with other object types.

use ES5's Array.isArray to test for true arrays.

Item 59: Avoid Excessive Coercion

  • avoid mixing coercions with overloading
  • consider defensively guarding against unexpected inputs

Item 60: Support Method Chaining

  • support method chaining by designing stateless methods what produce new objects
  • support method chaining in stateful methods by returning this

Chapter 7, Concurrency

Item 61: Don't Block the Event Queue on I/O

JavaScript is providing a run-to-completion grarantee: any user code that is currently running in a shared context is allowed to finish executing before the next event handler is invoked.

the drawback of run-to-completion is that any and all code you write effectively holds up the rest of the application from proceeding.

the single most important rule of concurrent JavaScript is never to use any blocking I/O APIs in the middle of an application's event queue.

Asynchronous APIs take callbacks to defer processing of expensive operations and avoid blocking the main application.

JavaScript accepts events concurrently but processes event handlers sequentially using an event queue.

Item 62: Use Nested or Named Callbacks for Asynchronous Sequencing

  • use nested or named callbacks to perform serveral asynchronous operations in sequence.
  • avoid sequencing operations that can be performed concurrently

Item 63: Be Aware of Dropped Errors

asynchronous APIs cannot throw exceptions because at the time an asynchronous error occurs, there is no obvious execution context to throw the exception to.

asynchronous APIs tend to represent errors as special arguments to callbacks, or take additional error-handling callbacks (errbacks)

avoid copying and pasting error-handling code by writing shared error-handling functions.

make sure to handle all error conditions explicitly to avoid dropped errors.

Item 64: Use Recursion for Asynchronous Loops

  • loops cannot be asynchronous.
  • use recursive functions to perform iterations in separate turns of the event loop.
  • recursion performed in separate turns of the event loop does not overflow the call stack.

Item 65: Don't Block the Event Queue on Computation

to perform expensive computations, one simple approach is to use a concurrency mechanism like the web client platform's Worker API.

var ai = new Worker("ai.js");

this has the effect of spawning a new concurrent thread of execution with its own separate event queue, using the source file ai.js as the worker's script.

the application and worker can communicate with each other by sending messages to each other.

// application
var userMove = /* ... */;
ai.postMessage(JSON.stringify({
    userMove: userMove
}));
ai.onmessage = function(event) {
    executeMove(JSON.parse(event.data).computerMove);
};
// ai.js
self.onmessage = function(event) {
    // parse user move
    var userMove = JSON.parse(event.data).userMove;
    // generate next computer move
    var computerMove = computeNextMove(userMove);
    // format the computer move
    var message = JSON.stringify({
        computerMove: computerMove
    });
    self.postMessage(message);
};
function computeNextMove(userMove) {
    // ...
}

when the Worker API is not available or is too costly, consider breaking up computations across multiple turns of the event loop.

Item 66: Use a Counter to Perform Concurrent Operations

  • events in a JavaScript application occur nondeterministically, that is, in unpredictable order.
  • use a counter to avoid data races in concurrent operations.

Item 67: Never Call Asynchronous Callbacks Synchronously

  • never call an asynchronous callback synchronously, even if the data is immediately available.
  • calling an asynchronous callback synchronously disrupts the expected sequence of operations and can lead to unexpected interleaving of code.
  • calling an asynchronous callback synchronously can lead to stack overflows or mishandled exceptions.
  • use a asynchronous API such as setTimeout to schedule an synchronous callback to run in another turn.
function downloadCachingAsync(url, onsuccess, onerror) {
    if (cache.has(url)) {
        onsuccess(cache.get(url)); // synchronous call
        return;
    }
    return downloadAsync(url, function(file) {
        cache.set(url, file);
        onsuccess(file);
    }, onerror);
}

to ensure the callback is always invoked asynchronously, use setTimeout :

// ...
var cached = cache.get(url);
setTimeout(onsuccess.bind(null, cached), 0);
return;
//...

Item 68: Use Promises for Cleaner Asynchronous Logic

Search Blog: