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,Here's the HTML for the above JavaScript:
// 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
<!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
A minor correction: in Python 2.6, at least, strings of equal value are equal.
>>> a = 'abc'
>>> b = 'abc'
>>> a == b
True
>>> a = "foo"
>>> b = "f"
>>> b += "oo"
>>> a == b, a is b
(True, False)
:)
Thanks ;)