Home

Awesome

Notes on Douglas Crockford's Javascript the Good Parts :rocket:

These are notes on the book Javascript the Good Parts which was published in 2008, before es6. There have not been any revised editions of the book published at the time of writing, see https://www.github.com/dwyl/Javascript-the-Good-Parts-notes/issues/33 for the ongoing conversation on this.

This book calls out the best parts of Javascript and tells you what to avoid (the 'bad parts'). It's about making sure you know the really important parts of the language and create good habits instead of having to break bad ones down the line.

The idea of these notes is to collect the excellent information from an already dense book into note form, translating these ideas into 'plain English' where possible and adding explanations throughout where the book might not make things obvious as it assumes prior knowledge.

You can buy the book or follow Douglas Crockford on Github.

contributions welcome Please don't hesitate to submit PRs to improve these notes wherever you feel something can be clarified, improved on or have an example added to it.

Table of Contents

<a name="chapter1"/> Chapter 1 - Good Parts

Most programming languages contain good parts and bad parts. I discovered that I could be a better programmer by using only the good parts and avoiding the bad parts. After all, how can you build something good out of bad parts?

The best parts of Javascript include:

The worst parts include global variables - there is a common global object namespace where they're all lumped together and they're essential to the language.

Javascript has a class free object makeup, relying instead on objects inheriting properties directly from other objects - this is prototypal inheritance.

<a name="chapter2"/> Chapter 2 - Grammar

Always use // for comments, even multi-line ones to avoid having to escape /* characters.

Numbers

Number methods are discussed in Chapter 8.

Strings

Single quotes are often used to define a String in JavaScript, but if a person's name has an apostrophe (and the developer does not know the difference between an apostrophe and single quote) it is useful to "escape" the apostrophe character:

var name = 'Patrick O\'Brian'; // using a backslash in front of the apostrophe
console.log('name:', name); // name: Patrick O'Brian

further reading: https://webdesignledger.com/common-typography-mistakes-apostrophes-versus-quotation-marks

BackSlashExample

String methods are discussed in Chapter 8.

Statements

for (myvariable in object) {
		if (object.hasOwnProperty(myvariable)) {
		... //statements to be executed
		}
}

Expressions

Literals

Functions

<a name="chapter3"/> Chapter 3 - Objects

Javascript simple types:

All other values are objects including arrays and functions.

Objects are class free, can contain other objects and can inherit properties from their prototypes (which can reduce object initialisation time and memory consumption).

Object Literals


var empty_object = {};

var today = {
	day: "Wednesday",
	month: "April",
	year: 2014,

	weather: { //objects can contain nested objects like this one
		morning: "sunny",
		afternoon: "cloudy"
	}
}

Retrieval

Update

Reference

Prototype

More details in Chapter 6

Reflection

today.hasOwnProperty('number')  //will return true
today.hasOwnProperty('constructor')   //will return false

Enumeration

var i;
var properties = [
	'day', 'month', 'year'
	];
var today = {
  day: '5',
  month: 'September',
  year: '2001'
};	
for (i = 0; i < properties.length; i++) {
	document.writeIn(properties[i] + ':' + today[properties[i]]);
}
//OUTPUT
// day:5
// month:September
// year:2001

Delete

Global Abatement

var MYAPP = {}

MYAPP.today = {
	day: "Wednesday",
	month: "April",
	year: 2014,

	weather: { //objects can contain nested objects like this one
		morning: "sunny",
		afternoon: "cloudy"
	}
}
//Making sure all other variables (like today) are contained within this one global variable (MYAPP) means none of them have global scope and therefore the risk of naming conflicts, etc in your application is reduced

<a name="chapter4"/> Chapter 4 - Functions

The best thing about JavaScript is its implementation of functions.

Function Objects

Function Literal

//Format of a function
function name (parameterA, parameterB){
	statements;
}
<a name="nestedFunctions"/> * Functions can be nested within functions and the inner function can access all the parameters of the outer function as well as its own

Invocation

Method Invocation Pattern

myObject.incrementFunction();

Function Invocation Pattern

var sum = add(3, 4);

Workaround: Artificially create a new this:

myObject.double = function() {
	//in the book, the var here is called `that` but name changed for clarity
	var globalScopeThis = this; //workaround

	var innerFunc = function() {
		globalScopeThis.value = add(globalScopeThis.value, globalScopeThis.value);
	};

	innerFunc(); //invoke innerFunc as function
};

myObject.double();
console.log(myObject.value);

Constructor Invocation Pattern

//create a function Quo that takes a string - Quo will be our prototype function as we will see
var Quo = function (string){
	this.status = string;
}

//Now create a get_status method for Quo - this will be a public method
Quo.prototype.get_status = function () {
	return this.status;
}

//create a new instance of Quo using the prefix NEW
var myQuo = new Quo("happy");

//because of the use of the new prefix, myQuo is an instance of Quo which means it can access the public method get_status from it's prototype
document.writeIn(myQuo.get_status());     //returns 'happy'

Apply Invocation Pattern

var array = [5, 2]    //will be the parameters for our function
var sum = add.apply(null, array);     //value of 'this' is null and value of sum is 7 as the 'apply' method passes 5 and 2 to the 'add' method

Arguments

//inside the function
for (i = 0; i < arguments.length; i++) {
	dosomething;  //e.g. sum +=arguments[i]
}

Return

Exceptions

//Thinking through what exceptions could happen in an add function, the main function contains the throw statement with the exception object
var add = function (a,b) {
	if (typeof a !== 'number' || typeof b !== 'number'){
		throw {
			name: 'TypeError';
			message: 'The add function requires numbers';
		}
	}
	return a + b;
}
//When you use the function later on, add a try block with a catch clause to catch the exception object
var try_it = function () {
	try{
		add("seven");   //will throw an exception as it is not a number
	}
	catch (e) {
		document.writeIn(e.name + ':' + e.message);
	}
}

try_it();    //you could rewrite this function so the argument is passed in here where it is invoked

Augmenting Types

String.method ('trim', function () {
	return this.replace(/ˆ\s+|\s+$/g, '');     //uses regular expression
});
//Makes a method available to all functions, ONLY when it definitely does not already exist

Function.prototype.method (methodName, func) {
	if (!this.prototype[methodName]){
		this.prototype[methodName] = func;
		return this;
	}
};

Recursion

var variable = function functionName (parameters){
	//wrap the statements to be executed and the recursive call in a loop statement so it doesn't recurse forever
	//statements to be executed in the function;
	functionName(arguments);
};

functionName (initialArguments); //initial call to the function

Scope

Closure

Callbacks

request = prepare_the_request();
send_request_asynchronously(request, function(response){     //function being passed in as a parameter
	display(response);
});

<a name="Module"/> Module

var Serial_maker = function() {

	//all variables defined in this object are now fixed and hidden from anything outside this function
	//see page 42 of book for full example
};
//calls to methods passing them parameters are made here

Cascade

Curry

//set up a simple function that we will customise with curry
var add = function (a,b){
	return a + b;
}

var addTen = add.curry(10);      //passes 10 as the first argument to the add() function
addTen(20);                     //The use of the curry method in addTen means addTen === add(10, 20);
Function.method('curry', function() {
	var slice = Array.prototype.slice,
		args = slice.apply(arguments),
		that = this;
	return function () {
		return that.apply(null, args.concat(slice.apply(arguments)));
	}
});

Memoization

var memoizer = function (memo, fundamental) {
	var shell = function (n) {
		var result = memo[n];
		if (typeof result !== 'number') {
			result = fundamental(shell, n);
			memo[n] = result;
		}
		return result;
	}
	return shell;
}

<a name="chapter5"/> Chapter 5 - Inheritance

Javascript is a prototypal language, which means that objects inherit directly from other objects

Main benefit of inheritance is code reuse - you only have to specify differences.

Javascript can mimic classical inheritance but has a much richer set of code reuse patterns

Pseudoclassical

Object Specifiers

Rather than: var myObject = maker (f, l, m, c, s) which has too many parameters to remember in the right order, use an object specifier:

var myObject = maker ({      //note curly braces
	first: f,
	last: l,
	state: s,
	city: c
	}
;)

to contain them. They can now be listed in any order

Also useful to pass object specifiers to JSON (see Appendix E notes)

Prototypal

Functional

var mammal = function (spec) {
	var that = {};    //that is a new object which is basically a container of 'secrets' shared to the rest of the inheritance chain

	that.get_name = function () {
		return spec.name;
	};

	that.says = function () {
		return spec.saying || '';  //returns an empty string if no 'saying' argument is passed through the spec object when calling mammal
	};
	return that;     //returns the object that contains the now private properties and methods (under functional scope)
}

var myMammal = mammal({name: 'Herb'});

Creating an object 'cat' can now inherit from the mammal constructor and only pay attention to the differences between it and mammal:

var cat = function (spec) {
	spec.saying = spec.saying || 'meow';   //if spec.saying doesn't already exists, make it 'meow'
	var that = mammal(spec);      //here the object 'container of secrets' is set up inheriting from mammal already

	//functions and property augmentations happen here

	return that;      //as above
}

Parts

<a name="chapter6"/> Chapter 6 - Arrays

Javascript only has array-like objects which are slower than 'real' arrays.

Retrieval and updating of properties works the same as with an object except with integer property names.

Arrays have their own literal format and their own set of methods (Chapter 8 - Methods).

Array Literals

Length

Delete

Enumeration

Confusion

The rule is simple: when the property names [keys] are small sequential integers, you should use an array. Otherwise, use an object.

var is_array = function (value) {
	return Object.prototype.toString.apply(value) === '[object Array]';
	//apply(value) binds `value` to `this` & returns true if `this` is an array
}

Methods

//capital A in Array means this refers to the prototype
Array.method('reduce', function (parameters){
	//define variables and function
	//return a value
});

Dimensions

<a name="chapter7"/> Chapter 7 - Regular Expressions

A regular expression is the specification of the syntax of a simple language

Used with regexp.exec, regexp.test, string.match, string.replace, string.search and string.split to interact with string (more in Chapter 8 - Methods)

Quite convoluted and difficult to read as they do not allow comments or whitespace so a JavaScript regular expression must be on a single line

An Example

/ˆ(?:([A-Za-z]+):)?(\/{0,3})([0-9.\-A-Za-z]+)(?::(\d+))?(?:\/([ˆ?#]*))?(?:\?([ˆ#]*))?(?:#(.*))?$/

Breaking it down one portion (factor) at a time:

Another example /ˆ-?\d+(?:\.\d*)?(?:e[+\-]?\d+)?$/i;

Most of this we have seen before but here are the new bits:

Construction

3 flags exist in regular expressions: i means insensitive - ignore the character case, g means global - to match multiple items and m means multiline - where ˆ and $ can match line-ending characters

Two ways to build a regular expression:

  1. Regular Expression literals as per the examples above start and end with a slash /
    • Here the flags are appended after the final slash, for example /i
    • Be careful: RegExp objects made by regular expression literals share a single instance
  2. Use RegExp constructor
    • The first parameter is the string to be made into a RegExp object, the second is the flag
    • Useful when all information for creating the regular expression is not available at time of programming
    • Backslashes mean something in the constructor, so these must be doubled and quotes must be escaped
//example creating a regular expression object that matches a JavaScript string

var my_regexp = new RegExp("'(?:\\\\.|[ˆ\\\\\\'])*'", 'g');

Elements

Regexp Choice

| provides a match if any of the sequences provided match.

In "into".match(/in|int/);, the in will be a match so it doesn't even look at the int.

Regexp Sequence

A regexp sequence is made up of one or more regexp factors. If there are no quantifiers after the factor (like ?, * or +), the factor will be matched one time.

<a name="Factors"/> Regexp Factor

A regexp factor can be a character, a parenthesized group, a character class, or an escape sequence.

It's essentially a portion of the full RegExp, like what we broke down the regexp above into.

Regexp Escape

As well as escaping special characters in regexp factors, the backslash has additional uses:

*\b is a bad part. It was supposed to be a word-boundary anchor but is useless for multilingual applications

Regexp Group

Four kinds of groups:

<a name="RegexpClass"/> Regexp Class

Regexp Class Escape

There are specific characters that must be escaped in a character class: - / [ \ ] ˆ

Regexp Quantifier

A quantifier at the end of a factor indicates how many times the factor should be matched

Prefer to use 'zero or more' or 'one or more' matching over the 'zero or one' matching - i.e. prefer greedy matching over lazy matching

<a name="chapter8"/> Chapter 8 - Methods

Arrays

array.concat(item...)

Produces new array copying the original array with the items appended to it (does not modify the original array like array.push(item) does). If the item is an array, its elements are appended.

array.join(separator)

Creates a string of all the array's elements, separated by the separator. Use an empty string separator ('') to join without separation.

array.pop()

Removes last element of array. Returns undefined for empty arrays.

array.push(item...)

Modifies the array, appending items onto the end. Returns the new length of the array.

array.reverse()

Modifies the array by reversing the order of the elements.

array.shift()

Removes the first element of the array (does not leave a hole in the array - same effect as using the .splice(a,b) method) and returns that first element.

array.slice(start, end)

Different to splice.

'slice' creates a new array, copying from the start element and stopping at the element before the end value given. If no end is given, default is array.length.

Negative values for start and end will have array.length added to them and if start>end, it will return an empty array.

array.sort(comparefn)

JavaScript has a sort() method which was created only to compare strings and therefore sorts numbers incorrectly (it will sort them as 1, 15, 2, 23, 54 for example). Therefore, we have to write a comparison function which returns 0 if the two elements you are comparing are equal, a positive number if the first element should come first and a negative number if the second element should come first. Then pass this comparison function to sort() as a parameter to allow it to sort array elements intelligently.

Page 80-82 in the book takes you through various iterations of the comparison functions - for numbers, simple strings, objects and objects with multiple keys (for example if you want to sort objects by first and last names). These should be taken from the book when required.

array.splice(start, deleteCount, item...)

Removes elements from the array making sure there are no holes left in the array. It is most popularly used for deleting elements from an array.

It removes the deleteCount number of elements from the array starting from the start position. If there are item parameters passed to it, it will replace the deleted elements in the array with the items.

It returns an array containing the deleted elements.

array.unshift(item...)

Works like push but adds items to the front of the array instead of the end. Returns the new length of the array.

Function

function.apply(thisArg, [argArray])

The apply method invokes a function, passing in the object that will be bound to this and optional array of arguments.

Number

number.toExponentional(fractionDigits)

Converts number to a string in exponential form (e.g. 3.14e+0). fractionDigits (from 0 to 20) gives the number of decimal places.

number.toFixed(fractionDigits)

Converts number to a string in decimal form (e.g. 3.1415927). fractionDigits (from 0 to 20) gives the number of decimal places.

number.toPrecision(precision)

Converts number to a string in decimal form (e.g. 3.1415927). The difference from toFixed is that precision (from 0 to 21) gives the number of total digits.

number.toString(radix)

Converts number to a string. radix is an optional parameter between 2 and 36 and gives the base. The default radix is 10.

Object

object.hasOwnProperty(name)

Does not look at the property chain. Returns true if the object contains the property name.

RegExp

regexp.exec(string)

Most powerful (and slowest) regexp method.

Checks the string against the regexp (starting at position 0) and returns an array containing the matches. The regexp is set up with various capturing groups and these determine the elements that go in the array:

If the regexp contains a g flag (e.g. var regexp = /[ˆ<>]+|<(\/?)([A-Za-z]+)([ˆ<>]*)>/g;), there is a lot more to look out for:

Example on page 87 of the book is worth reading to improve understanding.

regexp.test(string)

Simplest (and fastest) regexp method.

If regexp matches the string it returns true. Otherwise it returns false. Do not use the g flag with this method.

String

string.charAt(pos)

Returns character at position pos in the string starting from 0. If pos is less than zero or bigger than the string itself it return an empty string.

string.charCodeAt(pos)

Same as charAt except it returns the integer that represents the code point value of the character at position pos. Returns NaN if string.length < pos < 0.

string.concat(string...)

Creates new string concatenating various strings. + tends to be used instead of this method (e.g. var cat = 'c'+'a'+'t';)

string.indexOf(searchString, position)

Searches for searchString within string starting at position position (an optional parameter). If position is not provided, search starts at the beginning of the string. Returns the integer position of the first matched character or -1 if no match is found.

string.lastIndexOf(searchString, position)

Same as indexOf but searches from the end of the string instead of the beginning.

string.localeCompare(that)

Compares string to that parameter and returns:

NB. 'a' < 'A', comparison is not just in length.

string.match(regexp)

Works the same way as regexp.exec(string) if there is no g flag in the regexp.

If there is a g flag in teh regexp, it produces an array of the matches but excludes the capturing groups

string.replace(searchValue, replaceValue)

Searches for the searchValue in string and replaces it with the replaceValue.

If searchValue is a:

If replaceValue is a:

string.search(regexp)

Similar to .indexOf(string) but takes a regexp instead of a string, returning the position of the first match (or -1 if there is no match). The g flag is ignored.

string.slice(start, end)

Creates a new string by copying the characters from the start position to the character before the end position in string.

The end parameter is optional and defaults to string.length. If either parameter is negative, string.length is added to it.

string.split(separator, limit)

Creates an array of strings by splitting apart string at the points where the separator appears (e.g. if the separator is '.', ab.cd' becomes ['ab', 'cd']).

string.substring(start, end)

No reason to use, use slice instead.

string.toLocaleLowerCase()

Produces a new string converted to lower case, using the rules for the particular locale (geography).

string.toLocaleUpperCase()

Produces a new string converted to upper case, using the rules for the particular locale (geography).

string.toLowerCase()

Produces a new string converted to lower case.

string.toUpperCase()

Produces a new string converted to upper case.

String.fromCharCode(char...)

Produces a new string from a series of numbers. var a = String.fromCharCode(67, 97, 116); //a === 'Cat' NB. You're calling the prototype here, not replacing 'String' with your own variable.

<a name="chapter9"/> Chapter 9 - Style

JavaScripts's loose typing and excessive error tolerance provide little compile-time assurance of our programs' quality, so to compensate, we should code with strict discipline.

the likelihood a program will work [as intended] is significantly enhanced by our ability to read it

I use a single global variable to contain an application or library. Every object has its own namespace, so it is easy to use objects to organize my code. Use of closure provides further information hiding, increasing the strength of my modules.

<a name="chapter10"/> Chapter 10 - Beautiful Features

Each feature you add to something has a lot of different costs (documentation costs, specification, design, testing and development costs) and these are often not properly accounted for.

Features that offer value to a minority of users impose a cost on all users

We cope with the complexity of feature-driven design by finding and sticking with the good parts. For example, microwaves do a ton of different things, but most people just use one setting, the timer and the clock. So why not design with just the good parts?

<a name="AppendixA"/> Appendix A - the Awful Parts

Need to know what all the pitfalls are with these parts.

Global variables

These are variables that are visible throughout the code in any scope. They can be changed at any time by any part of the program which makes them unreliable in larger complex programs. This can also lead to naming conflicts which can cause your code to fail or you to accidentally overwrite a global variable.

Defined in three ways:

Scope

Although JavaScript has block syntax (i.e. is written in blocks) like a lot of other programming languages, it has functional scope and not block scope.

Variables should all be declared at the top of the function and not littered throughout the block.

<a name="SemicolonInsertion"/> Semicolon Insertion

Attempts to correct faulty programs by automatically inserting semicolons. Do not depend on this as it can hide underlying issues.

Also ensure opening curly braces ({) are on the first line of a statement, otherwise semicolons will be erroneously inserted and cause problems:

//Ensure curly braces open on the first line of a statement
return {
	status: true	//for example
};
//instead of
return
{
	status:true
};

Reserved Words

Most JavaScript reserved words are not used in the language but cannot be used to name variables or parameters.

If used as the key in object literals, they must be quoted. For example object - {'case' : value}; or object['final'] = value; as case and final are both reserved words.

Unicode

JavaScript characters are 16 bits which only cover the original Unicode Basic Multilingual Place.

typeof

Watch out for:

All objects are truthy and null is falsy, so you can use the following to tell them apart:

if (my_value && typeof my_value === 'object') {
	//then my value is definitely an object or an array because not only is its 'typeof' an object but it's also truthy (first statement)
}

NaN

For numbers, best use your own isNumber formula:

var isNumber = function isNumber(value) {
	return typeof value === 'number' && isFinite(value);	//isFinite() rejects NaN and Infinity, but is only good for numbers, not strings
}

Phony Arrays

JavaScript doesn't have real arrays, it has array-like objects.

To test if value is an array:

if (my_value && typeof my_value === 'object' && typeof my_value.length === 'number' &&
	!(my_value.propertyIsEnumerable('length'))) {
		//my_value is definitely an array!
}

The arguments array isn't an array, just an object with a length property.

Falsy Values

0, NaN, '', false, null and undefined are all falsy values, but they are not interchangeable. When testing for a missing member of an object for example, you need to use undefined and not null.

undefined and NaN are actually global variables instead of constants but don't change their values.

Object

JavaScript objects inherit members from the prototype chain so they are never truly empty.

To test for membership without prototype chain involvement, use the hasOwnProperty method or limit your results (for example, to specific types like number so you know you're not dragging in object members from up the prototype for example if that's what's causing the problem).

<a name="AppendixB"/> Appendix B - the Bad Parts

Avoid these altogether

The function statement vs the function expression: To use JavaScript well, important to understand that functions are values.

Typed wrappers: Don't use new Boolean or new String or new Number, it's completely unnecessary. Also avoid new Object and new Array and use {} and [] instead.

<a name="new"/>new operator: Functions that are intended to be used with new (conventionally starting with a capital letter) should be avoided (don't define them) as they can cause all kinds of issues and complex bugs which are difficult to catch.

void: In JavaScript, this actually takes a value and returns undefined, which is hugely confusing and not helpful. Don't use it.

<a name="AppendixC"/> Appendix C - JSLint

JSLint is a code quality tool for JavaScript which checks your syntax.

Having read through this appendix (you can read more about JSLint here), I tend more towards JSHint, a fork of JSLint. It allows programmers to customise for themselves which the good parts and bad parts are and define their own subset, although naturally there are a number of pre-defined options. This is a really fantastic article on using JSHint; it's simple and aimed at having you using JSHint in a few minutes as well as providing various sources for pre-defined subsets.

Interesting article on prototypes: https://sporto.github.io/blog/2013/02/22/a-plain-english-guide-to-javascript-prototypes/