Functions are JavaScript’s workhorse, serving simultaneously as the programmer’s primary abstraction facility and implementation mechanism. Functions alone play roles that other languages fulfill with multiple distinct features: procedures, methods, constructors, and even classes and modules. Once you become comfortable with the finer points of functions, you have mastered a significant portion of JavaScript. The flip side of the coin is that it can take some time to learn how to use functions effectively in different contexts.
If you’re familiar with object-oriented programming, you’re likely accustomed to thinking of functions, methods, and class constructors as three separate things. In JavaScript, these are just three different usage patterns of one single construct: functions.
The simplest usage pattern is the function call:
function hello(username) {
return "hello, " + username;
}
hello("Keyser Söze"); // "hello, Keyser Söze"
This does exactly what it looks like: It calls the hello function and binds the name parameter to its given argument.
Methods in JavaScript are nothing more than object properties that happen to be functions:
var obj = {
hello: function() {
return "hello, " + this.username;
},
username: "Hans Gruber"
};
obj.hello(); // "hello, Hans Gruber"
Notice how hello refers to this to access the properties of obj. You might be tempted to assume that this gets bound to obj because the hello method was defined on obj. But we can copy a reference to the same function in another object and get a different answer:
var obj2 = {
hello: obj.hello,
username: "Boo Radley"
};
obj2.hello(); // "hello, Boo Radley"
What really happens in a method call is that the call expression itself determines the binding of this, also known as the call’s receiver. The expression obj.hello() looks up the hello property of obj and calls it with receiver obj. The expression obj2.hello() looks up the hello property of obj2—which happens 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.
Since methods are nothing more than functions called on a particular object, there is no reason why an ordinary function can’t refer to this:
function hello() {
return "hello, " + this.username;
}
This can be useful for predefining a function for sharing among multiple objects:
var obj1 = {
hello: hello,
username: "Gordon Gekko"
};
obj1.hello(); // "hello, Gordon Gekko"
var obj2 = {
hello: hello,
username: "Biff Tannen"
};
obj2.hello(); // "hello, Biff Tannen"
However, a function that uses this is not particularly useful to call as a function rather than a method:
hello(); // "hello, undefined"
Rather unhelpfully, a nonmethod function call provides the global object as the receiver, which in this case has no property called name and produces undefined. Calling a method as a function rarely does anything useful if the method depends on this, since there is no reason to expect the global object to match the expectations that the method has of the object it is called on. In fact, binding to the global object is a problematic enough default that ES5’s strict mode changes the default binding of this to undefined:
function hello() {
"use strict";
return "hello, " + this.username;
}
hello(); // error: cannot read property "username" of undefined
This helps catch accidental misuse of methods as plain functions by failing more quickly, since attempting to access properties of undefined immediately throws an error.
The third use of functions is as constructors. Just like methods and plain functions, constructors are defined with function:
function User(name, passwordHash) {
this.name = name;
this.passwordHash = passwordHash;
}
Invoking User with the new operator treats it as a constructor:
var u = new User("sfalken",
"0ef33ae791068ec64b502d6cb0191387");
u.name; // "sfalken"
Unlike function calls and method calls, a constructor call passes a brand-new object as the value of this, and implicitly returns the new object as its result. The constructor function’s primary role is to initialize the object.
• Method calls provide the object in which the method property is looked up as their receiver.
• Function calls provide the global object (or undefined for strict functions) as their receiver. Calling methods with function call syntax is rarely useful.
• Constructors are called with new and receive a fresh object as their receiver.
Higher-order functions used to be a shibboleth of the monks of functional programming, an esoteric term for what seemed like an advanced programming technique. Nothing could be further from the truth. Exploiting the concise elegance of functions can often lead to simpler and more succinct code. Over the years, scripting languages have adopted these techniques and in the process taken much of the mystery out of some of the best idioms of functional programming.
Higher-order functions are nothing more than functions that take other functions as arguments or return functions as their result. Taking a function as an argument (often referred to as a callback function because it is “called back” by the higher-order function) is a particularly powerful and expressive idiom, and one that JavaScript programs use heavily.
Consider the standard sort method on arrays. In order to work on all possible arrays, the sort method relies on the caller to determine how to compare any two elements in an array:
function compareNumbers(x, y) {
if (x < y) {
return -1;
}
if (x > y) {
return 1;
}
return 0;
}
[3, 1, 4, 1, 5, 9].sort(compareNumbers); // [1, 1, 3, 4, 5, 9]
The standard library could have required the caller to pass in an object with a compare method, but since only one method is required, taking a function directly is simpler and more concise. In fact, the above example can be simplified further with an anonymous function:
[3, 1, 4, 1, 5, 9].sort(function(x, y) {
if (x < y) {
return -1;
}
if (x > y) {
return 1;
}
return 0;
}); // [1, 1, 3, 4, 5, 9]
Learning to use higher-order functions can often simplify your code and eliminate tedious boilerplate. Many common operations on arrays have lovely higher-order abstractions that are worth familiarizing yourself with. Consider the simple act of transforming an array of strings. With a loop, we’d write:
var names = ["Fred", "Wilma", "Pebbles"];
var upper = [];
for (var i = 0, n = names.length; i < n; i++) {
upper[i] = names[i].toUpperCase();
}
upper; // ["FRED", "WILMA", "PEBBLES"]
With the handy map method of arrays (introduced in ES5), we can completely eliminate the loop details, implementing just the element-by-element transformation with a local function:
var names = ["Fred", "Wilma", "Pebbles"];
var upper = names.map(function(name) {
return name.toUpperCase();
});
upper; // ["FRED", "WILMA", "PEBBLES"]
Once you get the hang of using higher-order functions, you can start identifying opportunities to write your own. The telltale sign of a higher-order abstraction waiting to happen is duplicate or similar code. For example, imagine we found one part of a program constructing a string with the letters of the alphabet:
var aIndex = "a".charCodeAt(0); // 97
var alphabet = "";
for (var i = 0; i < 26; i++) {
alphabet += String.fromCharCode(aIndex + i);
}
alphabet; // "abcdefghijklmnopqrstuvwxyz"
Meanwhile, another part of the program generates a string containing numeric digits:
var digits = "";
for (var i = 0; i < 10; i++) {
digits += i;
}
digits; // "0123456789"
Still elsewhere, the program creates a random string of characters:
var random = "";
for (var i = 0; i < 8; i++) {
random += String.fromCharCode(Math.floor(Math.random() * 26)
+ aIndex);
}
random; // "bdwvfrtp" (different result each time)
Each example creates a different string, but they all share common logic. Each loop creates a string by concatenating the results of some computation to create each individual segment. We can extract the common parts and move them into a single utility function:
function buildString(n, callback) {
var result = "";
for (var i = 0; i < n; i++) {
result += callback(i);
}
return result;
}
Notice how the implementation of buildString contains all the common parts of each loop, but uses a parameter in place of the parts that vary: The number of loop iterations becomes the variable n, and the construction of each string segment becomes a call to the callback function. We can now simplify each of the three examples to use buildString:
var alphabet = buildString(26, function(i) {
return String.fromCharCode(aIndex + i);
});
alphabet; // "abcdefghijklmnopqrstuvwxyz"
var digits = buildString(10, function(i) { return i; });
digits; // "0123456789"
var random = buildString(8, function() {
return String.fromCharCode(Math.floor(Math.random() * 26)
+ aIndex);
});
random; // "ltvisfjr" (different result each time)
There are many benefits to creating higher-order abstractions. If there are tricky parts of the implementation, such as getting the loop boundary conditions right, they are localized to the implementation of the higher-order function. This allows you to fix any bugs in the logic just once, instead of having to hunt for every instance of the coding pattern spread throughout your program. If you find you need to optimize the efficiency of the operation, you again only have one place where you need to change anything. Finally, giving a clear name such as buildString to the abstraction makes it clearer to someone reading the code what the code does, without having to decode the details of the implementation.
Learning to reach for a higher-order function when you find yourself repeatedly writing the same patterns leads to more concise code, higher productivity, and improved readability. Keeping an eye out for common patterns and moving them into higher-order utility functions is an important habit to develop.
• Higher-order functions are functions that take other functions as arguments or return functions as their result.
• Familiarize yourself with higher-order functions in existing libraries.
• Learn to detect common coding patterns that can be replaced by higher-order functions.
Ordinarily, the receiver of a function or method (i.e., the value bound to the special keyword this) is determined by the syntax of its caller. In particular, the method call syntax binds the object in which the method was looked up to this. However, it is sometimes necessary to call a function with a custom receiver, and the function may not already be a property of the desired receiver object. It’s possible, of course, to add the method to the object as a new property:
obj.temporary = f; // what if obj.temporary already existed?
var result = obj.temporary(arg1, arg2, arg3);
delete obj.temporary; // what if obj.temporary already existed?
But this approach is unpleasant and even dangerous. It is often undesirable, and even sometimes impossible, to modify obj. Specifically, whatever name you choose for the temporary property, you run the risk of colliding with an existing property of obj. Moreover, some objects can be frozen or sealed, preventing the addition of any new properties. And more generally, it’s bad practice to go around arbitrarily adding properties to objects, particularly objects you didn’t create (see Item 42).
Luckily, functions come with a built-in call method for providing a custom receiver. Invoking a function via its call method:
f.call(obj, arg1, arg2, arg3);
behaves similarly to calling it directly:
f(arg1, arg2, arg3);
except that the first argument provides an explicit receiver object.
The call method comes in handy for calling methods that may have been removed, modified, or overridden. Item 45 shows a useful example, where the hasOwnProperty method can be called on an arbitrary object, even if the object is a dictionary. In a dictionary object, looking up hasOwnProperty produces an entry from the dictionary rather than an inherited method:
dict.hasOwnProperty = 1;
dict.hasOwnProperty("foo"); // error: 1 is not a function
Using the call method of the hasOwnProperty method makes it possible to call the method on the dictionary even though the method is not stored anywhere in the object:
var hasOwnProperty = {}.hasOwnProperty;
dict.foo = 1;
delete dict.hasOwnProperty;
hasOwnProperty.call(dict, "foo"); // true
hasOwnProperty.call(dict, "hasOwnProperty"); // false
The call method can also be useful when defining higher-order functions. A common idiom for a higher-order function is to accept an optional argument to provide as the receiver for calling the function. For example, an object that represents a table of key-value bindings might provide a forEach method:
var table = {
entries: [],
addEntry: function(key, value) {
this.entries.push({ key: key, value: value });
},
forEach: function(f, thisArg) {
var entries = this.entries;
for (var i = 0, n = entries.length; i < n; i++) {
var entry = entries[i];
f.call(thisArg, entry.key, entry.value, i);
}
}
};
This allows consumers of the object to use a method as the callback function f of table.forEach and provide a sensible receiver for the method. For example, we can conveniently copy the contents of one table into another:
table1.forEach(table2.addEntry, table2);
This code extracts the addEntry method from table2 (it could have even extracted the method from Table.prototype or table1), and the forEach method repeatedly calls addEntry with table2 as the receiver. Notice that even though addEntry only expects two arguments, forEach calls it with three: a key, value, and index. The extra index argument is harmless since addEntry simply ignores it.
• Use the call method to call a function with a custom receiver.
• Use the call method for calling methods that may not exist on a given object.
• Use the call method for defining higher-order functions that allow clients to provide a receiver for the callback.
Imagine that someone provides us with a function that calculates the average of any number of values:
average(1, 2, 3); // 2
average(1); // 1
average(3, 1, 4, 1, 5, 9, 2, 6, 5); // 4
average(2, 7, 1, 8, 2, 8, 1, 8); // 4.625
The average function is an example of what’s known as a variadic or variable-arity function (the arity of a function is the number of arguments it expects): It can take any number of arguments. By comparison, a fixed-arity version of average would probably take a single argument containing an array of values:
averageOfArray([1, 2, 3]); // 2
averageOfArray([1]); // 1
averageOfArray([3, 1, 4, 1, 5, 9, 2, 6, 5]); // 4
averageOfArray([2, 7, 1, 8, 2, 8, 1, 8]); // 4.625
The variadic version is more concise and arguably more elegant. Variadic functions have convenient syntax, at least when the caller knows ahead of time exactly how many arguments to provide, as in the examples above. But imagine that we have an array of values:
var scores = getAllScores();
How can we use the average function to compute their average?
average(/* ? */);
Fortunately, functions come with a built-in apply method, which is similar to their call method, but designed just for this purpose. The apply method takes an array of arguments and calls the function as if each element of the array were an individual argument of the call. In addition to the array of arguments, the apply method takes a first argument that specifies the binding of this for the function being called. Since the average function does not refer to this, we can simply pass it null:
var scores = getAllScores();
average.apply(null, scores);
If scores turns out to have, say, three elements, this will behave the same as if we had written:
average(scores[0], scores[1], scores[2]);
The apply method can be used on variadic methods, too. For example, a buffer object might contain a variadic append method for adding entries to its internal state (see Item 22 to understand the implementation of append):
var buffer = {
state: [],
append: function() {
for (var i = 0, n = arguments.length; i < n; i++) {
this.state.push(arguments[i]);
}
}
};
The append method can be called with any number of arguments:
buffer.append("Hello, ");
buffer.append(firstName, " ", lastName, "!");
buffer.append(newline);
With the this argument of apply, we can also call append with a computed array:
buffer.append.apply(buffer, getInputStrings());
Notice the importance of the buffer argument: If we passed a different object, the append method would attempt to modify the state property of the wrong object.
• Use the apply method to call variadic functions with a computed array of arguments.
• Use the first argument of apply to provide a receiver for variadic methods.
Item 21 describes a variadic average function, which can process an arbitrary number of arguments and produce their average value. How can we implement a variadic function of our own? The fixed-arity version, averageOfArray, is easy enough to implement:
function averageOfArray(a) {
for (var i = 0, sum = 0, n = a.length; i < n; i++) {
sum += a[i];
}
return sum / n;
}
averageOfArray([2, 7, 1, 8, 2, 8, 1, 8]); // 4.625
The definition of averageOfArray defines a single formal parameter, the variable a in the parameter list. When consumers call averageOfArray, they provide a single argument (sometimes called an actual argument to distinguish it clearly from the formal parameter), the array of values.
The variadic version is almost identical, but it does not define any explicit formal parameters. Instead, it makes use of the fact that JavaScript provides every function with an implicit local variable called arguments. The arguments object provides an array-like interface to the actual arguments: It contains indexed properties for each actual argument and a length property indicating how many arguments were provided. This makes the variable-arity average function expressible by looping over each element of the arguments object:
function average() {
for (var i = 0, sum = 0, n = arguments.length;
i < n;
i++) {
sum += arguments[i];
}
return sum / n;
}
Variadic functions make for flexible interfaces; different clients can call them with different numbers of arguments. But by themselves, they also lose a bit of convenience: If consumers want to call them with a computed array of arguments, they have to use the apply method described in Item 21. 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. This is usually easy to provide, because you can typically implement the variadic function as a small wrapper that delegates to the fixed-arity version:
function average() {
return averageOfArray(arguments);
}
This way, consumers of your functions don’t have to resort to the apply method, which can be less readable and often carries a performance cost.
• Use the implicit arguments object to implement variable-arity functions.
• Consider providing additional fixed-arity versions of the variadic functions you provide so that your consumers don’t need to use the apply method.
The arguments object may look like an array, but sadly it does not always behave like one. Programmers familiar with Perl and UNIX shell scripting are accustomed to the technique of “shifting” elements off of the beginning of an array of arguments. And JavaScript’s arrays do in fact contain a shift method, which removes the first element of an array and shifts all the subsequent elements over by one. But the arguments object itself is not an instance of the standard Array type, so we cannot directly call arguments.shift().
Thanks to the call method, you might expect to be able to extract the shift method from an array and call it on the arguments object. This might seem like a reasonable way to implement a function such as callMethod, which takes an object and a method name and attempts to call the object’s method on all the remaining arguments:
function callMethod(obj, method) {
var shift = [].shift;
shift.call(arguments);
shift.call(arguments);
return obj[method].apply(obj, arguments);
}
But this function does not behave even remotely as expected:
var obj = {
add: function(x, y) { return x + y; }
};
callMethod(obj, "add", 17, 25);
// error: cannot read property "apply" of undefined
The reason why this fails is that the arguments object is not a copy of the function’s arguments. In particular, all named arguments are aliases to their corresponding indices in the arguments object. So obj continues to be an alias for arguments[0] and method for arguments[1], even after we remove elements from the arguments object via shift. This means that while we appear to be extracting obj["add"], we are actually extracting 17[25]! At this point, everything begins to go haywire: Thanks to the automatic coercion rules of JavaScript, this promotes 17 to a Number object, extracts its "25" property (which does not exist), produces undefined, and then unsuccessfully attempts to extract the "apply" property of undefined to call it as a method.
The moral of this story is that the relationship between the arguments object and the named parameters of a function is extremely brittle. Modifying arguments runs the risk of turning the named parameters of a function into gibberish. The situation is complicated even further by ES5’s strict mode. Function parameters in strict mode do not alias their arguments object. We can demonstrate the difference by writing a function that updates an element of arguments:
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
As a consequence, it is much safer never to modify the arguments object. This is easy enough to avoid by first copying its elements to a real array. A simple idiom for implementing the copy is:
var args = [].slice.call(arguments);
The slice method of arrays makes a copy of an array when called without additional arguments, and its result is a true instance of the standard Array type. The result is guaranteed not to alias anything, and has all the normal Array methods available to it directly.
We can fix the callMethod implementation by copying arguments, and since we only need the elements after obj and method, we can pass a starting index of 2 to slice:
function callMethod(obj, method) {
var args = [].slice.call(arguments, 2);
return obj[method].apply(obj, args);
}
At last, callMethod works as expected:
var obj = {
add: function(x, y) { return x + y; }
};
callMethod(obj, "add", 17, 25); // 42
• Never modify the arguments object.
• Copy the arguments object to a real array using [].slice.call(arguments) before modifying it.
An iterator is an object providing sequential access to a collection of data. A typical API provides a next method that provides the next value in the sequence. Imagine we wish to write a convenience function that takes an arbitrary number of arguments and builds an iterator for those values:
var it = values(1, 4, 1, 4, 2, 1, 3, 5, 6);
it.next(); // 1
it.next(); // 4
it.next(); // 1
The values function must accept any number of arguments, so we construct our iterator object to iterate over the elements of the arguments object:
function values() {
var i = 0, n = arguments.length;
return {
hasNext: function() {
return i < n;
},
next: function() {
if (i >= n) {
throw new Error("end of iteration");
}
return arguments[i++]; // wrong arguments
}
};
}
But this code is broken, which becomes clear as soon as we attempt to use an iterator object:
var it = values(1, 4, 1, 4, 2, 1, 3, 5, 6);
it.next(); // undefined
it.next(); // undefined
it.next(); // undefined
The problem is due to the fact that a new arguments variable is implicitly bound in the body of each function. The arguments object we are interested in is the one associated with the values function, but the iterator’s next method contains its own arguments variable. So when we return arguments[i++], we are accessing an argument of it.next instead of one of the arguments of values.
The solution is straightforward: Simply bind a new local variable in the scope of the arguments object we are interested in, and make sure that nested functions only refer to that explicitly named variable:
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++];
}
};
}
var it = values(1, 4, 1, 4, 2, 1, 3, 5, 6);
it.next(); // 1
it.next(); // 4
it.next(); // 1
• Be aware of the function nesting level when referring to arguments.
• Bind an explicitly scoped reference to arguments in order to refer to it from nested functions.
With no distinction between a method and a property whose value is a function, it’s easy to extract a method of an object and pass the extracted function directly as a callback to a higher-order function. But it’s also easy to forget that an extracted function’s receiver is not bound to the object it was taken from. Imagine a little string buffer object that stores strings in an array that can be concatenated later:
var buffer = {
entries: [],
add: function(s) {
this.entries.push(s);
},
concat: function() {
return this.entries.join("");
}
};
It might seem possible to copy an array of strings into the buffer by extracting its add method and calling it repeatedly on each element of the source array using the ES5 forEach method:
var source = ["867", "-", "5309"];
source.forEach(buffer.add); // error: entries is undefined
But the receiver of buffer.add is not buffer. A function’s receiver is determined by how it is called, and we are not calling it here. Instead, we pass it to forEach, whose implementation calls it somewhere that we can’t see. As it turns out, the implementation of forEach uses the global object as the default receiver. Since the global object has no entries property, this code throws an error. Luckily, forEach also allows callers to provide an optional argument to use as the receiver of its callback, so we can fix this example easily enough:
var source = ["867", "-", "5309"];
source.forEach(buffer.add, buffer);
buffer.join(); // "867-5309"
Not all higher-order functions offer their clients the courtesy of providing a receiver for their callbacks. What if forEach did not accept the extra receiver argument? A good solution is to create a local function that makes sure to call buffer.add with the appropriate method call syntax:
var source = ["867", "-", "5309"];
source.forEach(function(s) {
buffer.add(s);
});
buffer.join(); // "867-5309"
This version creates a wrapper function that explicitly calls add as a method of buffer. Notice how the wrapper function itself does not refer to this at all. No matter how the wrapper function is called—as a function, as a method of some other object, or via call—it always makes sure to push its argument on the destination array.
Creating a version of a function that binds its receiver to a specific object is so common that ES5 added library support for the pattern. Function objects come with a bind method that takes a receiver object and produces a wrapper function that calls the original function as a method of the receiver. Using bind, we can simplify our example:
var source = ["867", "-", "5309"];
source.forEach(buffer.add.bind(buffer));
buffer.join(); // "867-5309"
Keep in mind that buffer.add.bind(buffer) creates a new function rather than modifying the buffer.add function. The new function behaves just like the old one, but with its receiver bound to buffer, while the old one remains unchanged. In other words:
buffer.add === buffer.add.bind(buffer); // false
This is a subtle but crucial point: It means that bind is safe to call even on a function that may be shared by other parts of a program. It is especially important for methods shared on a prototype object: The method will still work correctly when called on any of the prototype’s descendants. (See Chapter 4 for more on objects and prototypes.)
• Beware that extracting a method does not bind the method’s receiver to its object.
• When passing an object’s method to a higher-order function, use an anonymous function to call the method on the appropriate receiver.
• Use bind as a shorthand for creating a function bound to the appropriate receiver.
The bind method of functions is useful for more than just binding methods to receivers. Imagine a simple function for constructing URL strings from components:
function simpleURL(protocol, domain, path) {
return protocol + "://" + domain + "/" + path;
}
Frequently, a program may need to construct absolute URLs from site-specific path strings. A natural way to do this is with the ES5 map method on arrays:
var urls = paths.map(function(path) {
return simpleURL("http", siteDomain, path);
});
Notice how the anonymous function uses the same protocol string and the same site domain string on each iteration of map; the first two arguments to simpleURL are fixed for each iteration, and only the third argument is needed. We can use the bind method on simpleURL to construct this function automatically:
var urls = paths.map(simpleURL.bind(null, "http", siteDomain));
The call to simpleURL.bind produces a new function that delegates to simpleURL. As always, the first argument to bind provides the receiver value. (Since simpleURL does not refer to this, we can use any value; null and undefined are customary.) The arguments passed to simpleURL are constructed by concatenating the remaining arguments of simpleURL.bind to any arguments provided to the new function. In other words, when the result of simpleURL.bind is called with a single argument path, the function delegates to simpleURL("http", siteDomain, path).
The technique of binding a function to a subset of its arguments is known as currying, named after the logician Haskell Curry, who popularized the technique in mathematics. Currying can be a succinct way to implement function delegation with less boilerplate than explicit wrapper functions.
• Use bind to curry a function, that is, to create a delegating function with a fixed subset of the required arguments.
• Pass null or undefined as the receiver argument to curry a function that ignores its receiver.
Functions are a convenient way to store code as a data structure that can be executed later. This enables expressive higher-order abstractions such as map and forEach, and it is at the heart of JavaScript’s asynchronous approach to I/O (see Chapter 7). At the same time, it’s also possible to represent code as a string to pass to eval. Programmers are then confronted with a decision to make: Should code be represented as a function or as a string?
When in doubt, use a function. Strings are a much less flexible representation of code for one very important reason: They are not closures.
Consider a simple function for repeating a user-provided action multiple times:
function repeat(n, action) {
for (var i = 0; i < n; i++) {
eval(action);
}
}
At global scope, using this function will work reasonably well, because any variable references that occur within the string will be interpreted by eval as global variables. For example, a script that benchmarks the speed of a function might just use global start and end variables to store the timings:
var start = [], end = [], timings = [];
repeat(1000,
"start.push(Date.now()); f(); end.push(Date.now())");
for (var i = 0, n = start.length; i < n; i++) {
timings[i] = end[i] - start[i];
}
But this script is brittle. If we simply move the code into a function, then start and end are no longer global variables:
function benchmark() {
var start = [], end = [], timings = [];
repeat(1000,
"start.push(Date.now()); f(); end.push(Date.now())");
for (var i = 0, n = start.length; i < n; i++) {
timings[i] = end[i] - start[i];
}
return timings;
}
This function causes repeat to evaluate references to the global variables start and end. In the best case, one of the globals will be missing, and calling benchmark will throw a ReferenceError. If we’re really unlucky, the code will actually call push on some global objects that happen to be bound to start and end, and the program will behave unpredictably.
A more robust API accepts a function instead of a string:
function repeat(n, action) {
for (var i = 0; i < n; i++) {
action();
}
}
This way, the benchmark script can safely refer to local variables within a closure that it passes as the repeated callback:
function benchmark() {
var start = [], end = [], timings = [];
repeat(1000, function() {
start.push(Date.now());
f();
end.push(Date.now());
});
for (var i = 0, n = start.length; i < n; i++) {
timings[i] = end[i] - start[i];
}
return timings;
}
Another problem with eval is that high-performance engines typically have a harder time optimizing code inside a string, since the source code may not be available to the compiler early enough to optimize in time. A function expression can be compiled at the same time as the code it appears within, making it much more amenable to standard compilation.
• Never include local references in strings when sending them to APIs that execute them with eval.
• Prefer APIs that accept functions to call rather than strings to eval.
JavaScript functions come with a remarkable feature—the ability to reproduce their source code as a string:
(function(x) {
return x + 1;
}).toString(); // "function (x) {\n return x + 1;\n}"
Reflecting on the source code of a function is powerful, and clever hackers occasionally find ingenious ways to put it to use. But there are serious limitations to the toString method of functions.
First of all, the ECMAScript standard does not impose any requirements on the string that results from a function’s toString method. This means that different JavaScript engines will produce different strings, and may not even produce strings that bear any resemblance to the function.
In practice, JavaScript engines do attempt to provide a faithful representation of the source code of a function, as long as the function was implemented in pure JavaScript. An example of where this fails is with functions produced by built-in libraries of the host environment:
(function(x) {
return x + 1;
}).bind(16).toString(); // "function (x) {\n [native code]\n}"
Since in many host environments, the bind function is implemented in another programming language (typically C++), it produces a compiled function that has no JavaScript source code for the environment to reveal.
Because browser engines are allowed by the standard to vary in their output from toString, it is all too easy to write a program that works correctly in one JavaScript system but fails in another. JavaScript implementations will even make small changes (e.g., the whitespace formatting) that could break a program that is too sensitive to the exact details of function source strings.
Finally, the source code produced by toString does not provide a representation of closures that preserves the values associated with their inner variable references. For example:
(function(x) {
return function(y) {
return x + y;
}
})(42).toString(); // "function (y) {\n return x + y;\n}"
Notice how the resultant string still contains a variable reference to x, even though the function is actually a closure that binds x to 42.
These limitations make it difficult to depend on extracting function source in a manner that is both useful and reliable, and should generally be avoided. Very sophisticated uses of function source extraction should employ carefully crafted JavaScript parsers and processing libraries. But when in doubt, it’s safest to treat a JavaScript function as an abstraction that should not be broken.
• JavaScript engines are not required to produce accurate reflections of function source code via toString.
• Never rely on precise details of function source, since different engines may produce different results from toString.
• The results of toString do not expose the values of local variables stored in a closure.
• In general, avoid using toString on functions.
Many JavaScript environments have historically provided some capabilities to inspect the call stack: the chain of active functions that are currently executing (see Item 64 for more about the call stack). In some older host environments, every arguments object came with two additional properties: arguments.callee, which refers to the function that was called with arguments, and arguments.caller, which refers to the function that called it. The former is still supported in many environments, but it does not serve much of a purpose, short of allowing anonymous functions to refer to themselves recursively:
var factorial = (function(n) {
return (n <= 1) ? 1 : (n * arguments.callee(n - 1));
});
But this is not particularly useful, since it’s more straightforward for a function just to refer to itself by name:
function factorial(n) {
return (n <= 1) ? 1 : (n * factorial(n - 1));
}
The arguments.caller property is more powerful: It refers to the function that made the call with the given arguments object. This feature has since been removed from most environments out of security concerns, so it’s not reliable. Many JavaScript environments also provide a similar property of function objects—the nonstandard but widespread caller property, which refers to the function’s most recent caller:
function revealCaller() {
return revealCaller.caller;
}
function start() {
return revealCaller();
}
start() === start; // true
It is tempting to try to use this property to extract a stack trace: a data structure providing a snapshot of the current call stack. Building a stack trace seems deceptively simple:
function getCallStack() {
var stack = [];
for (var f = getCallStack.caller; f; f = f.caller) {
stack.push(f);
}
return stack;
}
For simple call stacks, getCallStack appears to work fine:
function f1() {
return getCallStack();
}
function f2() {
return f1();
}
var trace = f2();
trace; // [f1, f2]
But getCallStack is easily broken: If a function shows up more than once in the call stack, the stack inspection logic gets stuck in a loop!
function f(n) {
return n === 0 ? getCallStack() : f(n - 1);
}
var trace = f(1); // infinite loop
What went wrong? Since the function f calls itself recursively, its caller property is automatically updated to refer back to f. So the loop in getCallStack gets stuck perpetually looking at f. Even if we tried to detect such cycles, there’s no information about what function called f before it called itself—the information about the rest of the call stack is lost.
Each of these stack inspection facilities is nonstandard and limited in portability or applicability. Moreover, they are all explicitly disallowed in ES5 strict functions; attempted accesses to the caller or callee properties of strict functions or arguments objects throw an error:
function f() {
"use strict";
return f.caller;
}
f(); // error: caller may not be accessed on strict functions
The best policy is to avoid stack inspection altogether. If your reason for inspecting the stack is solely for debugging, it’s much more reliable to use an interactive debugger.
• Avoid the nonstandard arguments.caller and arguments.callee, because they are not reliably portable.
• Avoid the nonstandard caller property of functions, because it does not reliably contain complete information about the stack.