Skip to main content

Books: JavaScript: The Good Parts

These are some things that I found interesting, surprising, or profound when I read JavaScript: The Good Parts. Test your JavaScript Fu by seeing how many of these you already know!
// I'm doing my best to match Crockford's style.  The code passes JSLint,
// except in cases where I'm showing what *not* to do.
//
// I'm using a bunch of anonymous functions as blocks just so that the namespace
// doesn't get out of hand. Sorry if that's confusing.
//
// I'm allowing evil (i.e. eval) just so that I can use document.writeln. I'm
// not actually using eval itself anywhere.

/*jslint browser: true, evil: true */

// This is a method to create a method.

Function.prototype.method = function (name, func) {
this.prototype[name] = func;
return this;
};

// Create a new object using the given object as a prototype.

Object.beget = function (proto) {
var F = function () {};
F.prototype = proto;
return new F();
};

// Unlike Python and Ruby, two strings with the same value are considered
// identitical.

var s1 = 'c';
s1 += 'at';
var identical = s1 === 'cat'; // true
document.writeln('Strings with the same value are identical: ' + identical);

// Blocks don't introduce a new scope. Only functions do.

(function () {
if (true) {
var s = true;
}
document.writeln("Blocks don't have their own scope: " + s); // true
}());

// Test the for in loop.

(function () {
var prop,
obj = new Function();
obj.foo = 'bar';

document.writeln('Here it is with all the properties:');
for (prop in obj) {

// This has "method" defined from above.

document.writeln(" " + prop);
}

document.writeln('Filter out properties using hasOwnProperty:');
for (prop in obj) {

// This one doesn't.

if (obj.hasOwnProperty(prop)) {
document.writeln(" " + prop);
}
}

document.writeln('Filter out properties using typeof:');
for (prop in obj) {

// Neither does this one.

if (typeof obj[prop] !== 'function') {
document.writeln(" " + prop);
}
}
}());

// Test try and catch.

(function () {
document.writeln('Test exceptions:');
try {

// It's best to include a name and a message.'

throw {
name: 'TypeError',
message: 'number expected'
};
} catch (e) {
document.writeln(' name: ' + e.name);
document.writeln(' message: ' + e.message);
}
}());

// My buddy Leon Atkinson always says that if you don't have the identity
// property, then nothing makes sense. The reason JavaScript is so wonky is
// because all numbers are doubles, and NaN violates the identity property ;)

document.writeln("NaN === NaN: " + (NaN === NaN)); // false

// However, NaN is a big fat liar! NaN is "not a number", but if you ask it,
// it'll lie ;)

document.writeln("typeof NaN: " + typeof NaN); // number

// These are a bit strange.

document.writeln('typeof []: ' + (typeof [])); // Object
document.writeln('typeof null: ' + (typeof null)); // Object

// Unlike Java, all numbers are doubles even if they look like ints.

document.writeln("2 / 5: " + 2 / 5); // 0.4

// Even a function literal can use a name in case you need recursion.

(function function_literal() {
document.writeln("function_literal: " + function_literal);
}());

// Unlike Ruby, "" is falsy.

if ("") {
document.writeln("'' is truthy"); // nope
} else {
document.writeln("'' is falsy"); // yep
}

// Nested functions don't inherit "this" from the outer function. Instead, it's
// bound to the global object. Use "that" as a workaround.

var globalThis = this;

var objWithNestedFunction = {
test_nested_function_this: function () {
var that = this,
nested = function () {
document.writeln('In nested(), this === globalThis: ' +
(this === globalThis)); // true
document.writeln('In nested(), this === window: ' +
(this === window)); // true
document.writeln('In nested(), this === that: ' +
(this === that)); // false
};

nested();
}
};

objWithNestedFunction.test_nested_function_this();

// The book warns against using the constructor invocation pattern. If you
// forget to use "new", then "this" will silently be bound to the global object.
// If you do use them, it's imperative to capitalize them. It's a reminder
// to use "new".

(function () {
document.writeln('Called a constructor without new; this === globalThis: ' +
(this === globalThis)); // true
}());

// Use apply to pass a list of args and control what object should be used
// for "this".

var uses_this = function () {
var i;
document.writeln("uses_this.apply(...):");
document.writeln(" this.a: " + this.a); // 'a'
document.writeln(" arguments:");
for (i = 0; i < arguments.length; i += 1) {
document.writeln(" " + arguments[i]); // 1 and 2
}
};

var applyToThis = {
a: 'a'
};

uses_this.apply(applyToThis, ['1', '2']);

// The arguments function returns something that is array-like, but not a true
// array.

(function () {
document.writeln("typeof [].join: " + typeof [].join); // function
document.writeln("typeof arguments.join: " +
typeof arguments.join); // undefined
}(1, 2, 3));

// If you use a constructor with "new", you can return some object. If you
// don't, "this" is returned implicitly.

var ReturnSomethingElse = function () {
this.b = 'b';
return {
a: 'a',
'this': this
};
};

var somethingElse = new ReturnSomethingElse();
document.writeln("Returned something other than this:");
document.writeln(" somethingElse.a: " + somethingElse.a); // 'a'
document.writeln(" somethingElse['this'].b: " +
somethingElse['this'].b); // 'b'

// Instead of using beget, he recommends using "differential inheritance". See
// p. 53 for the full pattern.

var mammal = function (spec) {
var that = {};

that.speak = function () {
document.writeln("I'm mute.");
};

return that;
};

var cat = function (spec) {
var that = mammal(spec);

that.meow = function () {
document.writeln("Meow.");
};

return that;
};

var myCat = cat({some: 'keyword args'});
myCat.speak(); // I'm mute.
myCat.meow(); // Meow.

// He calls this a "part". In other languages, I think you'd call it a mixin.

var friendliness = function (that) {

that.speak = function () {
document.writeln("Hi! I'm " + that.name);
};

return that;
};

var person = {name: 'JJ'};
friendliness(person);
person.speak();

// "An array is a linear allocation of memory in which elements are accessed
// by integers that are used to compute offsets. Arrays can be very fast data
// structures. Unfortunately, JavaScript does not have anything like this
// kind of array." [p. 58]

// JavaScript implements arrays using hashes.

(function () {
var i,
array = [0, -1];

// Notice, this would be considered out of bounds using a real array.

document.writeln("array.length: " + array.length); // 2
array[100] = -100;
array[4] = -4;

// These will be all out of order, just like a normal hash.

document.writeln("array:");
for (i in array) {
if (array.hasOwnProperty(i)) {
document.writeln(" i: " + i + " array[i]: " + array[i] +
" typeof i: " + typeof i); // string
}
}
}());

// \b and \w can't handle I18N'ized text.

(function () {
var re = /^\b\w+\b$/;
document.writeln('aAa: ' + re.test('aAa')); // true
document.writeln('a\u00C1a: ' + re.test('a\u00C1a')); // false
}());

// By default, sort sorts numbers incorrectly.

(function () {
var numbers = [4, 8, 15, 16, 23, 42];
numbers.sort();
document.writeln("Incorrectly sorted numbers: ",
numbers.join(", ")); // 15, 16, 23, 4, 42, 8

// Here's how to sort them correctly.

numbers.sort(function (a, b) {
return a - b;
});
document.writeln("Correctly sorted numbers: ",
numbers.join(", ")); // 4, 8, 15, 16, 23, 42
}());

// Don't confuse slice(start, end) with splice(start, deleteCount, item...).
// slice uses end and is non-destructive. splice uses deleteCount and is
// destructive.

(function () {
var numbers = [0, 1, 2, 3, 4];
document.writeln("slice(1, 3): " + numbers.slice(1, 3)); // 1,2
document.writeln(" afterwards:" + numbers); // 0,1,2,3,4
document.writeln("splice(1, 3):" + numbers.splice(1, 3)); // 1,2,3
document.writeln(" afterwards:" + numbers); // 0,4
}());

// Some implementations suppress empty strings in the output array when the
// separator is a regular expression.

(function () {
var orig = "1,,3,4",
rejoined = orig.split(/,/).join(',');
document.writeln("Rejoined:" + rejoined); // 1,,3,4 on Firefox, at least.
}());

// I do believe the Java style guide suggests using a blank line after a
// multi-line if statement. Crockford suggests indenting the lines after the
// first line 8 spaces instead of 4. I think that destroys the parallelism.

if (true === false &&
false === true) {
document.writeln("We're done for.");
}

// You can use a normal function before it's even defined because of function
// hoisting, although the book never uses normally named functions.

(function () {
f(); // works
try {
g(); // fails
} catch (e) {
document.writeln("Calling g ahead of time didn't work.");
}

function f() {
document.writeln("Calling f ahead of time works.");
}

var g = function () {
document.writeln("Calling g ahead of time won't work.");
};
}());

// Here's a really fun example of function hoisting. f will get hoisted to the
// top in Safari but not in Mozilla.

(function () {
try {
f();
} catch (e) {
document.writeln("f was not hoisted."); // Mozilla
}
if (false) {
function f() {
document.writeln("f was hoisted."); // Safari
}
}
}());

// Crockford recommends using hanging braces to avoid the following problem
// when returning objects. JavaScript messes up the following because of
// semicolon insertion.

(function () {
var return_object = function () {
return // A semi-colon will be insered by JavaScript here.
{ // This is treated as a new statement.
a: 'a'
};
};

document.writeln("return_object returned undef: " +
(typeof return_object() === 'undefined')); // true
}());

// Always use a radix when using parseInt. Otherwise, numbers that start with
// '0' will parsed as octal.

(function () {
document.writeln("parseInt('09'): " + parseInt('09')); // 0
document.writeln("parseInt('09', 10): " + parseInt('09', 10)); // 9
}());

// JavaScript uses doubles for all numbers. Integer arithmetic is exact with
// doubles. If you're dealing with money, your best bet is to scale things
// (e.g. use an integer for pennies) so that the arithmetic will be exact.

(function () {
document.writeln("0.1 + 0.2 === 0.3: " + (0.1 + 0.2 === 0.3)); // false
document.writeln("1 + 2 === 3: " + (1 + 2 === 3)); // true
}());

// If you use an object to keep track of the number of times something has
// happened before, you should probably remember to use hasOwnProperty.
// Otherwise, when you go to increment the count for "constructor", you'll get
// this crazy bug.

(function () {
var counts = {},
word = "constructor";
if (counts[word]) {
counts[word] += 1;
} else {
counts[word] = 1;
}
document.writeln("counts[word]: " + counts[word]); // Some weird string.
}());

// Always use === and !== instead of == and !=. Otherwise, you may be
// surprised by the casting that == and != do.

document.writeln("' \\t\\r\\n ' == 0: " + (' \t\r\n ' == 0)); // true
Here's the HTML for the above JavaScript:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">

<html>
<body>
<pre><script src="program.js"></script></pre>
</body>
</html>

Comments

I also wrote a book review here: http://jjinux.blogspot.com/2009/03/books-javascript-good-parts-part-2.html
stephen said…
I love this summary.

A minor correction: in Python 2.6, at least, strings of equal value are equal.

>>> a = 'abc'
>>> b = 'abc'
>>> a == b
True
I said "Unlike Python and Ruby, two strings with the same value are considered identitical." Two strings with the same value in Python are equal, but not identical.

>>> a = "foo"
>>> b = "f"
>>> b += "oo"
>>> a == b, a is b
(True, False)

:)
> I love this summary.

Thanks ;)