Frequently Misunderstood JavaScript Concepts
This is a complete "reprint" of Appendix B from my book, Closure: The Definitive Guide. Even though my book was designed to focus on Closure rather than JavaScript in general, there were a number of pain points in the language that I did not think were covered well in other popular JavaScript books, such as JavaScript: The Good Parts or JavaScript: The Definitive Guide. So that the book did not lose its focus, I relegated this and my essay, "Inheritance Patterns in JavaScript," to the back of the book among the appendices. However, many people have told me anecdotally that Appendix B was the most valuable part of the book for them, so it seemed like this was worth sharing more broadly in hopes that it helps demystify a language that I have enjoyed so much.
This book is not designed to teach you JavaScript, but it does recognize that you are likely to have taught yourself JavaScript and that there are some key concepts that you may have missed along the way. This section is particularly important if your primary language is Java as the syntactic similarities between Java and JavaScript belie the differences in their respective designs.
JavaScript Objects are Associative Arrays whose Keys are Always Strings
Every object in JavaScript is an associative array whose keys are strings. This
is an important difference from other programming languages, such as Java,
where a type such as java.util.Map
is an associative array whose
keys can be of any type. When an object other than a string is used as a key in
JavaScript, no error occurs: JavaScript silently converts it to a string and
uses that value as the key instead. This can have surprising
results:
var foo = new Object(); var bar = new Object(); var map = new Object(); map[foo] = "foo"; map[bar] = "bar"; // Alerts "bar", not "foo". alert(map[foo]);
In the above example, map
does not map foo
to
"foo"
and bar
to
"bar"
. When foo
and bar
are
used as keys for map
, they are converted into strings using their
respective toString()
methods. This results in mapping the
toString()
of foo
to "foo"
and
the toString()
of bar
to
"bar"
. Because both foo.toString()
and
bar.toString()
are "[object Object]"
, the
above is equivalent
to:
var map = new Object(); map["[object Object]"] = "foo"; map["[object Object]"] = "bar"; alert(map["[object Object]"]);
Therefore, map[bar] = "bar"
replaces the mapping of
map[foo] = "foo"
on the previous
line.
There are Several Ways to Look Up a Value in an Object
There are several ways to look up a value in an object, so if you learned JavaScript by copy-and-pasting code from other web sites, it may not be clear that the following code snippets are equivalent:
// (1) Look up value by name: map.meaning_of_life; // (2) Look up value by passing the key as a string: map["meaning_of_life"]; // (3) Look up value by passing an object whose toString() method returns a // string equivalent to the key: var machine = new Object(); machine.toString = function() { return "meaning_of_life"; }; map[machine];
Note that the first approach, "Look up value by name," can only be
used when the name is a valid JavaScript identifier. Consider the example from
the previous section where the key was "[object
Object]"
:
alert(map.[object Object]); // throws a syntax error
This may lead you to believe that it is safer to always look up a value by passing a key as a string rather than by name. In Closure, this turns out not to be the case because of how variable renaming works in the Compiler. This will be explained in more detail in Chapter 13.
Single-quoted Strings and Double-quoted Strings are Equivalent
In some programming languages, such as Perl and PHP, double-quoted strings and single-quoted strings are interpreted differently. In JavaScript, both types of strings are interpreted in the same way; however, the convention in the Closure Library is to use single-quoted strings. (By comparison, Closure Templates mandate the use of single-quoted strings.) The consistent use of quotes makes it easier to perform searches over the codebase, but they make no difference to the JavaScript interpreter or the Closure Compiler.
The one caveat is that the JSON specification requires that strings be
double-quoted, so data that is passed to a strict JSON parser (rather than the
JavaScript eval()
method) must use double-quoted
strings.
There are Several Ways to Define an Object Literal
In JavaScript, the following statements are equivalent methods for creating a new, empty object:
// This syntax is equivalent to the syntax used in Java (and other C-style // languages) for creating a new object. var obj1 = new Object(); // Parentheses are technically optional if no arguments are passed to a // function used with the 'new' operator, though this is generally avoided. var obj2 = new Object; // This syntax is the most succinct and is used exclusively in Closure. var obj3 = {};
The third syntax is called an "object literal" because the properties of the object can be declared when the object is created:
// obj4 is a new object with three properties. var obj4 = { 'one': 'uno', 'two': 'dos', 'three': 'tres' }; // Alternatively, each property could be added in its own statement: var obj5 = {}; obj5['one'] = 'uno'; obj5['two'] = 'dos'; obj5['three'] = 'tres'; // Or some combination could be used: var obj6 = { 'two': 'dos' }; obj6['one'] = 'uno'; obj6['three'] = 'tres';
Note that when using the object literal syntax, each property is followed by a comma except for the last one. Care must be taken to keep track of commas, as it is often forgotten when later editing code to add a new property:
// Suppose the declaration of obj4 were changed to include a fourth property. var obj4 = { 'one': 'uno', 'two': 'dos', 'three': 'tres' // Programmer forgot to add a comma to this line... 'four': 'cuatro' // ...when this line was added. };
The above will result in an error from the JavaScript interpreter because it cannot parse the object literal due to the missing comma. Currently, all browsers other than Internet Explorer allow a trailing comma in object literals to eliminate this issue (support for the trailing comma is mandated in ES5, so it should appear in IE soon):
var obj4 = { 'one': 'uno', 'two': 'dos', 'three': 'tres', // This extra comma is allowed on Firefox, Chrome, and Safari. };
Unfortunately, the trailing comma produces a syntax error in Internet Explorer, so the Closure Compiler will issue an error when it encounters the trailing comma.
Because of the popularity of JSON, it is frequent to see the keys of object literals as double quoted strings. The quotes are required in order to be valid JSON, but they are not required in order to be valid JavaScript. Keys in object literals can be expressed in any of the following three ways:
var obj7 = { one: 'uno', // No quotes at all 'two': 'dos', // Single-quoted string "three": 'tres' // Double-quoted string };
Using no quotes at all may seem odd at first, particularly if there is a variable in scope with the same name. Try to predict what happens in the following case:
var one = 'ONE'; var obj8 = { one: one };
The above creates a new object, obj8
, with one property whose name
is one
and whose value is 'ONE'
. When
one
is used on the left of the colon, it is simply a name, but
when it is used on the right of the colon, it is a variable. This is perhaps
more obvious if obj8
were defined in the following
way:
var obj8 = {}; obj8.one = one;
Here it is clearer that obj8.one
identifies the property on
obj8
named one
which is distinct from the variable
one
to the right of the equals
sign.
The only time that quotes must be used with a key in an object literal is when the key is a JavaScript keyword (note this is no longer a restriction in ES5):
var countryCodeMap = { fr: 'France', in: 'India', // Throws a syntax error because 'in' is a JavaScript keyword ru: 'Russia' };
Despite this edge case, keys in object literals are rarely quoted in Closure. This has to do with variable renaming, which is explained in more detail in Chapter 13 on the Compiler. As a rule of thumb, only quote keys that would sacrifice the correctness of the code if they were renamed. For example, if the code were:
var translations = { one: 'uno', two: 'dos', three: 'tres' }; var englishToSpanish = function(englishWord) { return translations[englishWord]; }; englishToSpanish('two'); // should return 'dos'
Then the Compiler might rewrite this code as:
var a = { a: 'uno', b: 'dos', c: 'tres' }; var d = function(e) { return a[e]; }; d('two'); // should return 'dos' but now returns undefined
In this case, the behavior of the compiled code is different from that of the
original code, which is a problem. This is because the keys of
translations
do not represent properties that can be renamed, but
strings whose values are significant. Because the Compiler cannot reduce string
literals, defining translations
as follows would result in the
compiled code having the correct
behavior:
var translations = { 'one': 'uno', 'two': 'dos', 'three': 'tres' };
The "prototype" Property is Not the Prototype You are Looking For
For all the praise for its support of prototype-based programming, manipulating an object's prototype is not straightforward in JavaScript.
Recall that every object in JavaScript has a link to another object called its
prototype. Cycles are not allowed in a chain of prototype
links, so a collection of JavaScript objects and prototype relationships can be
represented as a rooted tree where nodes are objects and edges are prototype
relationships. Many modern browsers (though not all) expose an object's
prototype via its __proto__
property. (This causes a great deal of
confusion because an object's __proto__
and prototype
properties rarely refer to the same object.) The root of such a tree will be
the object referenced by Object.prototype
in JavaScript. Consider
the following JavaScript
code:
// Rectangle is an ordinary function. var Rectangle = function() {}; // Every function has a property named 'prototype' whose value is an object // with a property named 'constructor' that points back to the original // function. It is possible to add more properties to this object. Rectangle.prototype.width = 3; Rectangle.prototype.height = 4; // Creates an instance of a Rectangle, which is an object whose // __proto__ property points to Rectangle.prototype. This is discussed // in more detail in Chapter 5 on Classes and Inheritance. var rect = new Rectangle();
Figure B-1 contains the corresponding object model:
In the diagram, each box represents a JavaScript object and each circle
represents a JavaScript primitive. Recall that JavaScript objects are
associative arrays whose keys are always strings, so each arrow exiting a box
represents a property of that object, the target being the property's value.
For simplicity, the closed, shaded arrows represent a __proto__
property while closed, white arrows represent a prototype
property. Open arrows have their own label indicating the name of the
property.
The prototype chain for an object can be found by following the
__proto__
arrows until the root object is reached. Note that even
though Object.prototype
is the root of the graph when only
__proto__
edges are considered, Object.prototype
also
has its own values, such as the built-in function mapped to
hasOwnProperty
.
When resolving the value associated with a key on a JavaScript object, each
object in the prototype chain is examined until one is found with a property
whose name matches the specified key. If no such property exists, the value
returned is undefined
. This is effectively equivalent to the
following:
var lookupProperty = function(obj, key) { while (obj) { if (obj.hasOwnProperty(key)) { return obj[key]; } obj = obj.__proto__; } return undefined; };
For example, to evaluate the expression rect.width
, the first step
is to check whether a property named width
is defined on
rect
. From the diagram, it is clear that rect
has no
properties of its own because it has no outbound arrows besides
__proto__
. The next step is to follow the __proto__
property to Rectangle.prototype
which does have an outbound
width
arrow. Following that arrow leads to the primitive value
3
, which is what rect.width
evaluates
to.
Because the prototype chain always leads to Object.prototype
, any
value that is declared as a property on Object.prototype
will be
available to all objects, by default. For example, every object has a property
named hasOwnProperty
that points to a native function. That is,
unless hasOwnProperty
is reassigned to some other value on an
object, or some object in its prototype chain. For example, if
Rectangle.prototype.hasOwnProperty
were assigned to
alert
, then rect.hasOwnProperty
would refer to
alert
because Rectangle.prototype
appears earlier in
rect
's prototype chain than Object.prototype
.
Although this makes it possible to grant additional functionality to all
objects by modifying Object.prototype
, this practice is
discouraged and error-prone as explained in Chapter 4.
Understanding the prototype chain is also important when considering the effect
of removing properties from an object. JavaScript provides the
delete
keyword for removing a property from an object: using
delete
can only affect the object itself, but not any of the
objects in its prototype chain. This may sometimes yield surprising
results:
rect.width = 13; alert(rect.width); // alerts 13 delete rect.width; alert(rect.width); // alerts 3 even though delete was used delete rect.width; alert(rect.width); // still alerts 3
When rect.width = 13
is evaluated, it creates a new binding on
rect
with the key width
and the value
13
. When alert(rect.width)
is called on the following
line, rect
now has its own property named width
, so
it displays its associated value, 13
. When delete
rect.width
is called, the width
property defined on
rect
is removed, but the width
property on
Rectangle.prototype
still exists. This is why the second call to
alert
yields 3
rather than undefined
. To
remove the width
property from every instance of
Rectangle
, delete
must be applied to
Rectangle.prototype
:
delete Rectangle.prototype.width; alert(rect.width); // now this alerts undefined
It is possible to modify rect
so that it behaves as if it did not
have a width
property without modifying
Rectangle.prototype
by setting rect.width
to
undefined
. It can be determined whether the property was
overridden or deleted by using the built-in hasOwnProperty
method:
var obj = {}; rect.width = undefined; // Now both rect.width and obj.width evaluate to undefined even though obj // never had a width property defined on it or on any object in its prototype // chain. rect.hasOwnProperty('width'); // evaluates to true obj.hasOwnProperty('width'); // evaluates to false
Note that the results would be different if Rectangle
were
implemented as
follows:
var Rectangle2 = function() { // This adds bindings to each new instance of Rectangle2 rather than adding // them once to Rectangle2.prototype. this.width = 3; this.height = 4; }; var rect1 = new Rectangle(); var rect2 = new Rectangle2(); rect1.hasOwnProperty('width'); // evaluates to false rect2.hasOwnProperty('width'); // evaluates to true delete rect1.width; delete rect2.width; rect1.width; // evaluates to 3 rect2.width; // evaluates to undefined
Finally, note that the __proto__
properties in the diagram are not
set explicitly in the sample code. These relationships are managed behind the
scenes by the JavaScript
runtime.
The Syntax for Defining a Function is Significant
There are two common ways to define a function in JavaScript:
// Function Statement function FUNCTION_NAME() { /* FUNCTION_BODY */ } // Function Expression var FUNCTION_NAME = function() { /* FUNCTION_BODY */ };
Although the function statement is less to type and is commonly used by those new to JavaScript, the behavior of the function expression is more straightforward. (Despite this, the Google style guide advocates using the function expression, so Closure uses it in almost all cases.) The behavior of the two types of function definitions is not the same, as illustrated in the following examples:
function hereOrThere() { return 'here'; } alert(hereOrThere()); // alerts 'there' function hereOrThere() { return 'there'; }
It may be surprising that the second version of hereOrThere
is used
before it is defined. This is due to a special behavior of function statements
called hoisting which allows a function to be used before
it is defined. In this case, the last definition of hereOrThere()
wins, so it is hoisted and used in the call to
alert()
.
By comparison, a function expression associates a value with variable, just like any other assignment statement. Because of this, calling a function defined in this manner uses the function value most recently assigned to the variable:
var hereOrThere = function() { return 'here'; }; alert(hereOrThere()); // alerts 'here' hereOrThere = function() { return 'there'; };
For a more complete argument of why function expressions should be favored over function statements, see Appendix B of Douglas Crockford's JavaScript: The Good Parts (O'Reilly).
What "this" Refers to When a Function is Called
When calling a function of the form foo.bar.baz()
, the object
foo.bar
is referred to as the receiver. When
the function is called, it is the receiver that is used as the value for
this
:
var obj = {}; obj.value = 10; /** @param {...number} additionalValues */ obj.addValues = function(additionalValues) { for (var i = 0; i < arguments.length; i++) { this.value += arguments[i]; } return this.value; }; // Evaluates to 30 because obj is used as the value for 'this' when // obj.addValues() is called, so obj.value becomes 10 + 20. obj.addValues(20);
If there is no explicit receiver when a function is called, then the global
object becomes the receiver. As explained in "goog.global",
window
is the global object when JavaScript is executed in a web
browser. This leads to some surprising
behavior:
var f = obj.addValues; // Evaluates to NaN because window is used as the value for 'this' when // f() is called. Because and window.value is undefined, adding a number to // it results in NaN. f(20); // This also has the unintentional side-effect of adding a value to window: alert(window.value); // Alerts NaN
Even though obj.addValues
and f
refer to the same
function, they behave differently when called because the value of the receiver
is different in each call. For this reason, when calling a function that refers
to this
, it is important to ensure that this
will
have the correct value when it is called. To be clear, if this
were not referenced in the function body, then the behavior of
f(20)
and obj.addValues(20)
would be the
same.
Because functions are first-class objects in JavaScript, they can have their own
methods. All functions have the methods call()
and
apply()
which make it possible to redefine the receiver (i.e., the
object that this
refers to) when calling the function. The method
signatures are as
follows:
/** * @param {*=} receiver to substitute for 'this' * @param {...} parameters to use as arguments to the function */ Function.prototype.call; /** * @param {*=} receiver to substitute for 'this' * @param {Array} parameters to use as arguments to the function */ Function.prototype.apply;
Note that the only difference between call()
and
apply()
is that call()
receives the function
parameters as a individual arguments whereas apply()
receives them
as a single
array:
// When f is called with obj as its receiver, it behaves the same as calling // obj.addValues(). Both of the following increase obj.value by 60: f.call(obj, 10, 20, 30); f.apply(obj, [10, 20, 30]);
The following calls are equivalent, as f
and
obj.addValues
refer to the same
function:
obj.addValues.call(obj, 10, 20, 30); obj.addValues.apply(obj, [10, 20, 30]);
However, the following will not work: neither call()
nor
apply()
uses the value of its own receiver to substitute for the
receiver
argument when it is
unspecified.
// Both statements evaluate to NaN obj.addValues.call(undefined, 10, 20, 30); obj.addValues.apply(undefined, [10, 20, 30]);
The value of this
can never be null
or
undefined
when a function is called. When null
or
undefined
is supplied as the receiver
to
call()
or apply()
, the global object is used as the
value for receiver
instead. Therefore, the above has the same
undesirable side-effect of adding a property named value
to the
global
object.
It may he helpful to think of a function as having no knowledge of the variable
to which it is assigned. This helps reinforce the idea that the value of
this
will be bound when the function is called rather than when it
is
defined.
The "var" Keyword is Significant
Many self-taught JavaScript programmers believe that the var
keyword is optional because they do not observe different behavior when they
omit it. On the contrary, omitting the var
keyword can lead to
some very subtle
bugs.
The var
keyword is significant because it introduces a new variable
in local scope. When a variable is referenced without the var
keyword, it uses the variable by that name in the closest scope. If no such
variable is defined, a new binding for that variable is declared on the global
object. Consider the following
example:
var foo = 0; var f = function() { // This defines a new variable foo in the scope of f. // This is said to "shadow" the global variable foo, whose value is 0. // The global value of foo could be referenced via window.foo, if desired. var foo = 42; var g = function() { // This defines a new variable bar in the scope of g. // It uses the closest declaration of foo, which is in f. var bar = foo + 100; return bar; }; // There is no variable bar declared in the current scope, f, so this // introduces a new variable, bar, on the global object. Code in g has // access to f's scope, but code in f does not have access to g's scope. bar = 'DO NOT DO THIS!'; // Returns a function that adds 100 to the local variable foo. return g; }; // This alerts 'undefined' because bar has not been added to the global scope yet. alert(typeof bar); // Calling f() has the side-effect of introducing the global variable bar. var h = f(); alert(bar); // Alerts 'DO NOT DO THIS!' // Even though h() is called outside of f(), it still has access to scope of // f and g, so h() returns (foo + 100), or 142. alert(h()); // Alerts 142
This gets even trickier when var
is omitted from loop variables.
Consider the following function, f()
, which uses a loop to call
g()
three times. Calling g()
uses a loop to call
alert()
three times, so you may expect nine alert boxes to appear
when f()
is
called:
var f = function() { for (i = 0; i < 3; i++) { g(i); } }; var g = function() { for (i = 0; i < 3; i++) { alert(i); } } // This alerts 0, 1, 2, and then stops. f();
Instead, alert()
only appears three times because both
f()
and g()
fail to declare the loop variable
i
with the var
keyword. When g()
is
called for the first time, it uses the global variable i
which has
been initialized to 0
by f()
. When g()
exits, it has increased the value of i
to 3
. On the
next iteration of the loop in f()
, i
is now
3
, so the test for the conditional i < 3
fails,
and f()
terminates. This problem is easily solved by appropriately
using the var
keyword to declare each loop
variable:
for (var i = 0; i < 3; i++)
Understanding var
is important in avoiding subtle bugs related to
variable scope. Enabling Verbose warnings from the Closure Compiler will help
catch these
issues.
Block Scope is Meaningless
Unlike most C-style languages, variables in JavaScript functions are accessible throughout the entire function rather than the block (delimited by curly braces) in which the variable is declared. This can lead to the following programming error:
/** * Recursively traverses map and returns an array of all keys that are found. * @param {Object} map * @return {Array.<string>} */ var getAllKeys = function(map) { var keys = []; for (var key in map) { keys.push(key); var value = map[key]; if (typeof value == 'object') { // Here, "var map" does not introduce a new local variable named map // because such a variable already exists in function scope. var map = value; keys = keys.concat(getAllKeys(map)); } } return keys; }; var mappings = { 'derivatives': { 'sin x': 'cos x', 'cos x': '-sin x'}, 'integrals': { '2x': 'x^2', '3x^2': 'x^3'} }; // Evaluates to: ['derivatives', 'sin x', 'cos x', 'integrals'] getAllKeys(mappings);
The array returned by getAllKeys()
is missing the values
'2x'
and '3x^2'
. This is because of a subtle error
where map
is reused as a variable inside the if
block. In languages that support block scoping, this would introduce a new
variable named map
that would be assigned to value
for the duration of the if
block, and upon exiting the
if
block, the recent binding for map
would be
discarded and the previous binding for map
would be restored. In
JavaScript, there is already a variable named map
in scope because
one of the arguments to getAllKeys()
is named map
.
Even though declaring var map
within getAllKeys()
is
likely a signal that the programmer is trying to introduce a new variable, the
var
is silently ignored by the JavaScript interpreter and
execution proceeds without
interruption.
When the Verbose warning level is used, the Closure Compiler issues a warning
when it encounters code such as this. To appease the Compiler, either the
var
must be dropped (to indicate the existing variable is meant to
be reused) or a new variable name must be introduced (to indicate that a
separate variable is meant to be used). The getAllKeys()
example
falls into the latter case, so the if
block should be rewritten
as:
if (typeof value == 'object') { var newMap = value; keys = keys.concat(getAllKeys(newMap)); }
Interestingly, because the scope of a variable includes the entire function, the declaration of the variable can occur anywhere in the function, even after its first "use":
var strangeLoop = function(someArray) { // Will alert 'undefined' because i is in scope, but no value has been // assigned to it at this point. alert(i); // Assign 0 to i and use it as a loop counter. for (i = 0; i < someArray.length; i++) { alert('Element ' + i + ' is: ' + someArray[i]); } // Declaration of i which puts it in function scope. // The value 42 is never used. var i = 42; };
Like the case where redeclaring a variable goes unnoticed by the interpreter but is flagged by the Compiler, the Compiler will also issue a warning (again, with the Verbose warnings enabled) if a variable declaration appears after its first use within the function.
It should be noted that even though blocks do not introduce new scopes,
functions can be used in place of blocks for that purpose. The if
block in getAllKeys()
could be rewritten as
follows:
if (typeof value == 'object') { var functionWithNewScope = function() { // This is a new function, and therefore a new scope, so within this // function, map is a new variable because it is prefixed with 'var'. var map = value; // Because keys is not prefixed with 'var', the existing value of keys // from the enclosing function is used. keys = keys.concat(getAllKeys(map)); }; // Calling this function will have the desired effect of updating keys. functionWithNewScope(); }
Although the above will work, it is less efficient than replacing var
map
with var newMap
as described
earlier.