ptg 5 Functions J avaScript functions are powerful beasts. They are first class objects, meaning they can be assigned to variables and as properties, passed as arguments to functions, have properties of their own, and more. JavaScript also supports anonymous functions, commonly used for inline callbacks to other functions and object methods. In this chapter we will cover the somewhat theoretical side of JavaScript func- tions, providing us with the required background to easily dive into the more in- teresting uses of functions as we dig into into closures in Chapter 6, Applied Func- tions and Closures, and methods and functions as a means to implement objects in Chapter 7, Objects and Prototypal Inheritance. 5.1 Defining Functions Throughout the first part of this book we have already seen several ways to define functions. In this section we will go over the different ways JavaScript allows us to do so, and investigate their pros and cons as well as some unexpected browser differences. 5.1.1 Function Declaration The most straightforward way to define a function is by way of a function definition, seen in Listing 5.1. 73 From the Library of WoweBook.Com Download from www.eBookTM.com ptg 74 Functions Listing 5.1 A function declaration function assert(message, expr) { if (!expr) { throw new Error(message); } assert.count++; return true; } assert.count = 0; This is the assert function from Chapter 1, Automated Testing. The function declaration starts with the keyword function, followed by an identifier, assert in the above example. The function may define one or more formal parameters, i.e., named arguments. Finally, the function has a body enclosed in brackets. Functions may return a value. If no return statement is present, or if it’s present without an expression, the function returns undefined. Being first class objects, functions can also have properties assigned to them, evident by the count property in the above example. 5.1.2 Function Expression In addition to function declarations, JavaScript supports function expressions. A function expression results in an anonymous function that may be immediately exe- cuted, passed to another function, returned from a function, or assigned to a variable or an object property. In function expressions the identifier is optional. Listing 5.2 shows the assert function from before implemented as a function expression. Listing 5.2 An anonymous function expression var assert = function (message, expr) { if (!expr) { throw new Error(message); } assert.count++; return true; }; assert.count = 0; From the Library of WoweBook.Com Download from www.eBookTM.com ptg 5.1 Defining Functions 75 Note that in contrast to function declarations, function expressions—like any expression—should be terminated by a semicolon. Although not strictly necessary, automatic semicolon insertion can cause unexpected results, and best practices dictate that we always insert our own semicolons. This alternative implementation of the assert function differs somewhat from the previous one. The anonymous function has no name, and so can only refer to itself by way of arguments.callee or through the assert variable, accessible through the scope chain. We will discuss both the arguments object and the scope chain in more detail shortly. As noted previously, the identifier is optional in function expressions. Whether named function expressions are still anonymous functions is a matter of definition, but the functions stay anonymous to the enclosing scope. Listing 5.3 shows an example of a named function expression. We will discuss the implications and cross- browser issues surrounding named function expressions in Section 5.3.6, Function Expressions Revisited. Listing 5.3 A named function expression var assert = function assert(message, expr) { if (!expr) { throw new Error(message); } assert.count++; return true; }; assert.count = 0; 5.1.3 The Function Constructor JavaScript functions are first class objects, which means they can have properties, including methods, of their own. Like any other JavaScript object, functions have a prototype chain; functions inherit from Function.prototype, which in turn inherits from Object.prototype. 1 The Function.prototype object pro- vides a few useful properties, such as the call and apply methods. In addition to the properties defined by their prototypes, function objects have length and prototype properties. 1. The details of JavaScript’s prototypal inheritance are covered in Chapter 7, Objects and Prototypal Inheritance. From the Library of WoweBook.Com Download from www.eBookTM.com ptg 76 Functions In contrast to what one might expect, the prototype property is not a ref- erence to the function object’s internal prototype (i.e., Function.prototype). Rather, it is an object that will serve as the prototype for any object created by using the function as a constructor. Constructors will be covered in depth in Chapter 7, Objects and Prototypal Inheritance. The length property of a function indicates how many formal parameters it expects, sometimes referred to as the function’s arity. Listing 5.4 shows an example. Listing 5.4 Function objects length property TestCase("FunctionTest", { "test function length property": function () { assertEquals(2, assert.length); assertEquals(1, document.getElementById.length); assertEquals(0, console.log.length); // In Firebug } }); The test can be run with JsTestDriver by setting up a project including a con- figuration file as described in Chapter 3, Tools of the Trade. The benchmark method in Listing 4.9 in Chapter 4, Test to Learn, used the length property to determine if the benchmark should be called in a loop. If the function took no formal parameters, the benchmark function looped it; otherwise the number of iterations was passed to the function to allow it to loop on its own, avoiding the overhead of the function calls. Note in the above example how Firebug’s console.log method does not use formal parameters at all. Still, we can pass as many arguments as we want, and they are all logged. Chrome’s implementation of document.getElementById also has a length of 0. It turns out that formal parameters is only one of two ways to access arguments passed to a function. The Function constructor can be used to create new functions as well. It can either be called as a function, i.e., Function(p1, p2, , pn, body);,or used in a new expression, as in new Function(p1, p2, , pn, body); with equal results. Both expressions create a new function object, and accept as arguments any number of formal parameters the new function should accept along with an optional function body as a string. Calling the function with no arguments results in an anonymous function that expects no formal parameters and has no function body. Listing 5.5 shows an example of defining the assert function via the Function constructor called as a function. From the Library of WoweBook.Com Download from www.eBookTM.com ptg 5.2 Calling Functions 77 Listing 5.5 Creating a function via Function var assert = Function("message", "expr", "if (!expr) { throw new Error(message); }" + "assert.count++; return true;"); assert.count = 0; When creating functions this way, we can provide the formal parameters in a number of ways. The most straightforward way is to pass one string per parameter, as in the above example. However, we can also pass a single comma-separated string, or a mix of the two, i.e., Function("p1,p2,p3", "p4", body);. The Function constructor is useful when the function body needs to be dynamically compiled, such as when creating functions tailored to the running environment or a set of input values, which can result in highly performant code. 5.2 Calling Functions JavaScript offers two ways of calling a function—directly using parentheses or indirectly using the call and apply methods inherited from Function. prototype. Direct invocation works as one would expect, as seen in Listing 5.6. Listing 5.6 Calling a function directly assert("Should be true", typeof assert == "function"); When calling a function, JavaScript performs no check on the number of ar- guments. You can pass zero, one, or ten arguments to a function regardless of the number of formal parameters it specifies. Anyformal parameterthat doesnot receive an actual value will have undefined as its value. 5.2.1 The arguments Object All of a function’s arguments are available through the array-like object argu- ments. This object has a length property, denoting the number of received arguments, and numeric indexes from 0 to length - 1 corresponding to the ar- guments passed when calling the function. Listing 5.7 shows the assert function using this object rather than its formal parameters. From the Library of WoweBook.Com Download from www.eBookTM.com ptg 78 Functions Listing 5.7 Using arguments function assert(message, expr) { if (arguments.length < 2) { throw new Error("Provide message and value to test"); } if (!arguments[1]) { throw new Error(arguments[0]); } assert.count++; return true; } assert.count = 0; This is not a particularlyuseful way to use arguments, but shows howit works. In general, the arguments object should only be used when formal parameters cannot solve the problem at hand, because using it comes with a performance price. In fact, merely referencing the object will induce some overhead, indicating that browsers optimize functions that don’t use it. The arguments object is array-like only through its length property and numeric index properties; it does not provide array methods. Still, we can use array methods on it by utilizing Array.prototype.* and their call or apply methods. Listing 5.8 shows an example in which we create an array consisting of all but the first argument to a function. Listing 5.8 Using array methods with arguments function addToArray() { var targetArr = arguments[0]; var add = Array.prototype.slice.call(arguments, 1); return targetArr.concat(add); } As with arrays, the numerical indexes on the arguments object are really only properties with numbers for identifiers. Object identifiers are always converted to strings in JavaScript, which explains the code in Listing 5.9. From the Library of WoweBook.Com Download from www.eBookTM.com ptg 5.2 Calling Functions 79 Listing 5.9 Accessing properties with strings function addToArray() { var targetArr = arguments["0"]; var add = Array.prototype.slice.call(arguments, 1); return targetArr.concat(add); } Some browsers like Firefox optimize arrays, indeed treating numeric property identifiers as numbers. Even so, the properties can always be accessed by string identifiers. 5.2.2 Formal Parameters and arguments The arguments object shares a dynamic relationship with formal parameters; changing a property of the arguments object causes the corresponding formal parameter to change and vice versa as Listing 5.10 shows. Listing 5.10 Modifying arguments TestCase("FormalParametersArgumentsTest", { "test dynamic relationship": function () { function modify(a, b) { b = 42; arguments[0] = arguments[1]; return a; } assertEquals(42, modify(1, 2)); } }); Setting the formal parameter b to 42 causes arguments[1] to update ac- cordingly. Setting arguments[0] to this value in turn causes a to update as well. This relationship only exists for formal parameters that actually receive values. Listing 5.11 shows the same example in which the second argument is left out when calling the function. Listing 5.11 No dynamic mapping for missing parameters assertUndefined(modify(1)); From the Library of WoweBook.Com Download from www.eBookTM.com ptg 80 Functions In this example, the return value is undefined because setting b does not update arguments[1] when no value was passed to b. Thus, arguments[1] is still undefined, which causes arguments[0] to be undefined. a did receive a value and is still linked to the arguments object, meaning that the returned value is undefined. Not all browsers do this by the spec, so your mileage may vary with the above examples. This relationship may be confusing, and in some cases can be the source of mysterious bugs. A good piece of advice is to be careful when modifying function parameters, especially in functions that use both the formal parameters and the arguments object. In most cases defining a new variable is a much sounder strategy than tampering with formal parameters or arguments. For the reasons stated, ECMAScript 5, the next version of JavaScript, removes this feature in strict mode. Strict mode is discussed in detail in Chapter 8, ECMAScript 5th Edition. 5.3 Scope and Execution Context JavaScript only has two kinds of scope; global scope and function scope. This might be confusing to developers used to block scope. Listing 5.12 shows an example. Listing 5.12 Function scope "test scope": function () { function sum() { assertUndefined(i); assertException(function () { assertUndefined(someVar); }, "ReferenceError"); var total = arguments[0]; if (arguments.length > 1) { for (var i = 1, l = arguments.length; i < l; i++) { total += arguments[i]; } } assertEquals(5, i); return total; } sum(1, 2, 3, 4, 5); } From the Library of WoweBook.Com Download from www.eBookTM.com ptg 5.3 Scope and Execution Context 81 This example shows a few interesting aspects. The i variable is declared even before the var statement inside the for loop. Notice how accessing some arbi- trary variable will not work, and throws a ReferenceError (or TypeError in Internet Explorer). Furthermore, the i variable is still accessible, and has a value, after the for loop. A common error in methods that use more than one loop is to redeclare the i variable in every loop. In addition to global scope and function scope, the with statement can alter the scope chain for its block, but its usage is usually discouraged and it is effectively deprecated in ECMAScript 5 strict mode. The next version of ECMAScript, cur- rently a work-in-progress under the name of “Harmony”, is slated to introduce block scope with the let statement. let has been available as a proprietary extension to Mozilla’s JavaScript ™ since version 1.7, first released with Firefox 2.0. 5.3.1 Execution Contexts The ECMAScript specification describes all JavaScript code to operate in an ex- ecution context. Execution contexts are not accessible entities in JavaScript, but understanding them is vital to fully understand how functions and closures work. From the specification: “Whenever control is transferred to ECMAScript executable code, control is entering an execution context. Active execution contexts logically form a stack. The top execution context on this stack is the running execution context.” 5.3.2 The Variable Object An execution context has a variable object. Any variables and functions defined in- side the function are added as properties on this object. The algorithm that describes this process explain all of the examples in the previous section. • For any formal parameters, add corresponding properties on the variable object and let their values be the values passed as arguments to the function. • For any function declarations, add corresponding properties on the variable object whose values are the functions. If a function declaration uses the same identifier as one of the formal parameters, the property is overwritten. • For any variable declarations, add corresponding properties on the variable object and initialize the properties to undefined, regardless of how the variables are initialized in source code. If a variable uses the same identifier as an already defined property (i.e., a parameter or function), do not overwrite it. From the Library of WoweBook.Com Download from www.eBookTM.com ptg 82 Functions The effects of this algorithm is known as hoisting of functions and variable declarations. Note that although functions are hoisted in their entirety, variables only have their declaration hoisted. Initialization happens where defined in source code. This means that the code in Listing 5.12 is interpreted as Listing 5.13. Listing 5.13 Function scope after hoisting "test scope": function () { function sum() { var i; var l; assertUndefined(i); /* */ } sum(1, 2, 3, 4, 5); } This explains why accessing the i variable before the var statement yields undefined whereas accessing some arbitrary variable results in a reference error. The reference error is further explained by how the scope chain works. 5.3.3 The Activation Object The variable object does not explain why the arguments object is available inside the function. This object is a property of another object associated with execution contexts, the activation object. Note that both the activation object and the variable object are purely a specification mechanism, and cannot be reached by JavaScript code. For the purposes of identifier resolution, i.e., variable and function resolution, the activation object and the variable object are the same object. Because properties of the variable object are available as local variables inside an execution context, and because the variable object and the activation object is the same object, function bodies can reach the arguments object as if it was a local variable. 5.3.4 The Global Object Before running any code, the JavaScript engine creates a global object whose initial properties are the built-ins defined by ECMAScript, such as Object, String, Array and others, in addition to host defined properties. Browser implementations of JavaScript provide a property of the global object that is itself the global object, namely window. From the Library of WoweBook.Com Download from www.eBookTM.com . through the array-like object argu- ments. This object has a length property, denoting the number of received arguments, and numeric indexes from 0 to length - 1 corresponding to the ar- guments passed. ECMAScript, cur- rently a work-in-progress under the name of “Harmony”, is slated to introduce block scope with the let statement. let has been available as a proprietary extension to Mozilla’s JavaScript ™ since. JavaScript func- tions, providing us with the required background to easily dive into the more in- teresting uses of functions as we dig into into closures in Chapter 6, Applied Func- tions and