48
var f = function fact(x) { if (x <= 1) return 1; else return x*fact(x-1); };
// Function expressions can also be used as arguments to other functions:
data.sort(function(a,b) { return a-b; });
// Function expressions are sometimes defined and immediately invoked:
var tensquared = (function(x) {return x*x;}(10));
Note that the function name is optional for functions defined as expressions. A function
declaration statement actually declares a variable and assigns a function object to it. A
function definition expression, on the other hand, does not declare a variable. A name
is allowed for functions, like the factorial function above, that need to refer to them-
selves. If a function definition expression includes a name, the local function scope for
that function will include a binding of that name to the function object. In effect, the
function name becomes a local variable within the function. Most functions defined as
expressions do not need names, which makes their definition more compact. Function
definition expressions are particularly well suited for functions that are used only once,
as in the last two examples above.
Function Names
Any legal JavaScript identifier can be a function name. Try to choose function names
that are descriptive but concise. Striking the right balance is an art that comes with
experience. Well-chosen function names can make a big difference in the readability
(and thus maintainability) of your code.
Function names are often verbs or phrases that begin with verbs. It is a common con-
vention to begin function names with a lowercase letter. When a name includes multiple
words, one convention is to separate words with underscores
like_this()
; another
convention is to begin all words after the first with an uppercase letter
likeThis()
.
Functions that are supposed to be internal or hidden (and not part of a public API) are
sometimes given names that begin with an underscore.
In some styles of programming, or within well-defined programming frameworks, it
can be useful to give frequently used functions very short names. The client-side Java-
Script framework jQuery (covered in Chapter 19), for example, makes heavy use in its
public API of a function named
$()
(yes, just the dollar sign). (Recall from §2.4 that
dollar signs and underscores are the two characters besides letters and numbers that
are legal in JavaScript identifiers.)
As described in §5.3.2, function declaration statements are “hoisted” to the top of the
enclosing script or the enclosing function, so that functions declared in this way may
be invoked from code that appears before they are defined. This is not true for functions
defined as expressions, however: in order to invoke a function, you must be able to
refer to it, and you can’t refer to a function defined as an expression until it is assigned
to a variable. Variable declarations are hoisted (see §3.10.1, but assignments to those
variables are not hoisted, so functions defined with expressions cannot be invoked
before they are defined.
8.1 Defining Functions | 165
Core JavaScript
74
Notice that most, but not all, of the functions in Example 8-1 contain a
return
statement
(§5.6.4). The
return
statement causes the function to stop executing and to return the
value of its expression (if any) to the caller. If the
return
statement does not have an
associated expression, it returns the
undefined
value. If a function does not contain a
return
statement, it simply executes each statement in the function body and returns
the
undefined
value to the caller.
Most of the functions in Example 8-1 are designed to compute a value, and they use
return
to return that value to their caller. The
printprops()
function is different: its job
is to output the names and values of an object’s properties. No return value is necessary,
and the function does not include a
return
statement. The value of an invocation of
the
printprops()
function is always
undefined
. (Functions with no return value are
sometimes called procedures.)
8.1.1 Nested Functions
In JavaScript, functions may be nested within other functions. For example:
function hypotenuse(a, b) {
function square(x) { return x*x; }
return Math.sqrt(square(a) + square(b));
}
The interesting thing about nested functions is their variable scoping rules: they can
access the parameters and variables of the function (or functions) they are nested with-
in. In the code above, for example, the inner function
square()
can read and write the
parameters
a
and
b
defined by the outer function
hypotenuse()
. These scope rules for
nested functions are very important, and we’ll consider them again in §8.6.
As noted in §5.3.2, function declaration statements are not true statements, and the
ECMAScript specification only allows them as top-level statements. They can appear
in global code, or within other functions, but they cannot appear inside of loops, con-
ditionals, or
try/catch/finally
or
with
statements.
1
Note that this restriction applies
only to functions declared as statements. Function definition expressions may appear
anywhere in your JavaScript code.
8.2 Invoking Functions
The JavaScript code that makes up the body of a function is not executed when the
function is defined but when it is invoked. JavaScript functions can be invoked in four
ways:
• as functions,
• as methods,
1. Some JavaScript implementations relax this rule. Firefox, for example, allows “conditional function
declarations” that appear within
if
statements.
166 | Chapter 8: Functions
70
• as constructors, and
• indirectly through their
call()
and
apply()
methods.
8.2.1 Function Invocation
Functions are invoked as functions or as methods with an invocation expression
(§4.5). An invocation expression consists of a function expression that evaluates to a
function object followed by an open parenthesis, a comma-separated list of zero or
more argument expressions, and a close parenthesis. If the function expression is a
property-access expression—if the function is the property of an object or an element
of an array—then it is a method invocation expression. That case will be explained
below. The following code includes a number of regular function invocation
expressions:
printprops({x:1});
var total = distance(0,0,2,1) + distance(2,1,3,5);
var probability = factorial(5)/factorial(13);
In an invocation, each argument expression (the ones between the parentheses) is eval-
uated, and the resulting values become the arguments to the function. These values are
assigned to the parameters named in the function definition. In the body of the function,
a reference to a parameter evaluates to the corresponding argument value.
For regular function invocation, the return value of the function becomes the value of
the invocation expression. If the function returns because the interpreter reaches the
end, the return value is
undefined
. If the function returns because the interpreter exe-
cutes a
return
, the return value is the value of the expression that follows the
return
or
undefined
if the
return
statement has no value.
For function invocation in ECMAScript 3 and nonstrict ECMAScript 5, the invocation
context (the
this
value) is the global object. In strict mode, however, the invocation
context is
undefined
.
Functions written to be invoked as functions do not typically use the
this
keyword at
all. It can be used, however, to determine whether strict mode is in effect:
// Define and invoke a function to determine if we're in strict mode.
var strict = (function() { return !this; }());
8.2.2 Method Invocation
A method is nothing more than a JavaScript function that is stored in a property of an
object. If you have a function
f
and an object
o
, you can define a method named
m
of
o
with the following line:
o.m = f;
Having defined the method
m()
of the object
o
, invoke it like this:
o.m();
8.2 Invoking Functions | 167
Core JavaScript
Download from Wow! eBook <www.wowebook.com>
65
Or, if
m()
expects two arguments, you might invoke it like this:
o.m(x, y);
The code above is an invocation expression: it includes a function expression
o.m
and
two argument expressions,
x
and
y
. The function expression is itself a property access
expression (§4.4), and this means that the function is invoked as a method rather than
as a regular function.
The arguments and return value of a method invocation are handled exactly as descri-
bed above for regular function invocation. Method invocations differ from function
invocations in one important way, however: the invocation context. Property access
expressions consist of two parts: an object (in this case
o
) and a property name (
m
). In
a method invocation expression like this, the object
o
becomes the invocation context,
and the function body can refer to that object by using the keyword
this
. Here is a
concrete example:
var calculator = { // An object literal
operand1: 1,
operand2: 1,
add: function() {
// Note the use of the this keyword to refer to this object.
this.result = this.operand1 + this.operand2;
}
};
calculator.add(); // A method invocation to compute 1+1.
calculator.result // => 2
Most method invocations use the dot notation for property access, but property access
expressions that use square brackets also cause method invocation. The following are
both method invocations, for example:
o["m"](x,y); // Another way to write o.m(x,y).
a[0](z) // Also a method invocation (assuming a[0] is a function).
Method invocations may also involve more complex property access expressions:
customer.surname.toUpperCase(); // Invoke method on customer.surname
f().m(); // Invoke method m() on return value of f()
Methods and the
this
keyword are central to the object-oriented programming para-
digm. Any function that is used as a method is effectively passed an implicit argument—
the object through which it is invoked. Typically, a method performs some sort of
operation on that object, and the method-invocation syntax is an elegant way to express
the fact that a function is operating on an object. Compare the following two lines:
rect.setSize(width, height);
setRectSize(rect, width, height);
The hypothetical functions invoked in these two lines of code may perform exactly the
same operation on the (hypothetical) object
rect
, but the method-invocation syntax in
the first line more clearly indicates the idea that it is the object
rect
that is the primary
focus of the operation.
168 | Chapter 8: Functions
66
Method Chaining
When methods return objects, you can use the return value of one method invocation
as part of a subsequent invocation. This results in a series (or “chain” or “cascade”) of
method invocations as a single expression. When working with the jQuery library
(Chapter 19), for example, it is common to write code like this:
// Find all headers, map to their ids, convert to an array and sort them
$(":header").map(function() { return this.id }).get().sort();
When you write a method that does not have a return value of its own, consider having
the method return
this
. If you do this consistently throughout your API, you will enable
a style of programming known as method chaining
2
in which an object can be named
once and then multiple methods can be invoked on it:
shape.setX(100).setY(100).setSize(50).setOutline("red").setFill("blue").draw();
Don’t confuse method chaining with constructor chaining, which is described in
§9.7.2.
Note that
this
is a keyword, not a variable or property name. JavaScript syntax does
not allow you to assign a value to
this
.
Unlike variables, the
this
keyword does not have a scope, and nested functions do not
inherit the
this
value of their caller. If a nested function is invoked as a method, its
this
value is the object it was invoked on. If a nested function is invoked as a function
then its
this
value will be either the global object (non-strict mode) or
undefined
(strict
mode). It is a common mistake to assume that a nested function invoked as a function
can use
this
to obtain the invocation context of the outer function. If you want to access
the
this
value of the outer function, you need to store that value into a variable that is
in scope for the inner function. It is common to use the variable
self
for this purpose.
For example:
var o = { // An object o.
m: function() { // Method m of the object.
var self = this; // Save the this value in a variable.
console.log(this === o); // Prints "true": this is the object o.
f(); // Now call the helper function f().
function f() { // A nested function f
console.log(this === o); // "false": this is global or undefined
console.log(self === o); // "true": self is the outer this value.
}
}
};
o.m(); // Invoke the method m on the object o.
Example 8-5, in §8.7.4, includes a more realistic use of the
var self=this
idiom.
2. The term was coined by Martin Fowler. See http://martinfowler.com/dslwip/MethodChaining.html.
8.2 Invoking Functions | 169
Core JavaScript
64
8.2.3 Constructor Invocation
If a function or method invocation is preceded by the keyword
new
, then it is a
constructor invocation. (Constructor invocations were introduced in §4.6 and
§6.1.2, and constructors will be covered in more detail in Chapter 9.) Constructor
invocations differ from regular function and method invocations in their handling of
arguments, invocation context, and return value.
If a constructor invocation includes an argument list in parentheses, those argument
expressions are evaluated and passed to the function in the same way they would be
for function and method invocations. But if a constructor has no parameters, then
JavaScript constructor invocation syntax allows the argument list and parentheses to
be omitted entirely. You can always omit a pair of empty parentheses in a constructor
invocation and the following two lines, for example, are equivalent:
var o = new Object();
var o = new Object;
A constructor invocation creates a new, empty object that inherits from the
prototype
property of the constructor. Constructor functions are intended to initialize
objects and this newly created object is used as the invocation context, so the con-
structor function can refer to it with the
this
keyword. Note that the new object is used
as the invocation context even if the constructor invocation looks like a method invo-
cation. That is, in the expression
new o.m()
,
o
is not used as the invocation context.
Constructor functions do not normally use the
return
keyword. They typically initialize
the new object and then return implicitly when they reach the end of their body. In this
case, the new object is the value of the constructor invocation expression. If, however,
a constructor explicitly used the
return
statement to return an object, then that object
becomes the value of the invocation expression. If the constructor uses
return
with no
value, or if it returns a primitive value, that return value is ignored and the new object
is used as the value of the invocation.
8.2.4 Indirect Invocation
JavaScript functions are objects and like all JavaScript objects, they have methods. Two
of these methods,
call()
and
apply()
, invoke the function indirectly. Both methods
allow you to explicitly specify the
this
value for the invocation, which means you can
invoke any function as a method of any object, even if it is not actually a method of
that object. Both methods also allow you to specify the arguments for the invocation.
The
call()
method uses its own argument list as arguments to the function and the
apply()
method expects an array of values to be used as arguments. The
call()
and
apply()
methods are described in detail in §8.7.3.
170 | Chapter 8: Functions
54
8.3 Function Arguments and Parameters
JavaScript function definitions do not specify an expected type for the function pa-
rameters, and function invocations do not do any type checking on the argument values
you pass. In fact, JavaScript function invocations do not even check the number of
arguments being passed. The subsections that follow describe what happens when a
function is invoked with fewer arguments than declared parameters or with more ar-
guments than declared parameters. They also demonstrate how you can explicitly test
the type of function arguments if you need to ensure that a function is not invoked with
inappropriate arguments.
8.3.1 Optional Parameters
When a function is invoked with fewer arguments than declared parameters, the ad-
ditional parameters are set to the
undefined
value. It is often useful to write functions
so that some arguments are optional and may be omitted when the function is invoked.
To do this, you must be able to assign a reasonable default value to parameters that are
omitted. Here is an example:
// Append the names of the enumerable properties of object o to the
// array a, and return a. If a is omitted, create and return a new array.
function getPropertyNames(o, /* optional */ a) {
if (a === undefined) a = []; // If undefined, use a new array
for(var property in o) a.push(property);
return a;
}
// This function can be invoked with 1 or 2 arguments:
var a = getPropertyNames(o); // Get o's properties into a new array
getPropertyNames(p,a); // append p's properties to that array
Instead of using an
if
statement in the first line of this function, you can use the
||
operator in this idiomatic way:
a = a || [];
Recall from §4.10.2 that the
||
operator returns its first argument if that argument is
truthy and otherwise returns its second argument. In this case, if any object is passed
as the second argument, the function will use that object. But if the second argument
is omitted (or
null
or another falsy value is passed), a newly created empty array will
be used instead.
Note that when designing functions with optional arguments, you should be sure to
put the optional ones at the end of the argument list so that they can be omitted. The
programmer who calls your function cannot omit the first argument and pass the sec-
ond: she would have to explicitly pass
undefined
the first argument. Also note the use
of the comment
/* optional */
in the function definition to emphasize the fact that
the parameter is optional.
8.3 Function Arguments and Parameters | 171
Core JavaScript
65
8.3.2 Variable-Length Argument Lists: The Arguments Object
When a function is invoked with more argument values than there are parameter
names, there is no way to directly refer to the unnamed values. The Arguments object
provides a solution to this problem. Within the body of a function, the identifier
arguments
refers to the Arguments object for that invocation. The Arguments object is
an array-like object (see §7.11) that allows the argument values passed to the function
to be retrieved by number, rather than by name.
Suppose you define a function
f
that expects to be passed one argument,
x
. If you invoke
this function with two arguments, the first argument is accessible within the function
by the parameter name
x
or as
arguments[0]
. The second argument is accessible only
as
arguments[1]
. Furthermore, like true arrays,
arguments
has a
length
property that
specifies the number of elements it contains. Thus, within the body of the function
f
,
invoked with two arguments,
arguments.length
has the value 2.
The Arguments object is useful in a number of ways. The following example shows
how you can use it to verify that a function is invoked with the expected number of
arguments, since JavaScript doesn’t do this for you:
function f(x, y, z)
{
// First, verify that the right number of arguments was passed
if (arguments.length != 3) {
throw new Error("function f called with " + arguments.length +
"arguments, but it expects 3 arguments.");
}
// Now do the actual function...
}
Note that it is often unnecessary to check the number of arguments like this. Java-
Script’s default behavior is fine in most cases: missing arguments are
undefined
and
extra arguments are simply ignored.
One important use of the Arguments object is to write functions that operate on any
number of arguments. The following function accepts any number of numeric argu-
ments and returns the value of the largest argument it is passed (see also the built-in
function
Math.max()
, which behaves the same way):
function max(/* ... */) {
var max = Number.NEGATIVE_INFINITY;
// Loop through the arguments, looking for, and remembering, the biggest.
for(var i = 0; i < arguments.length; i++)
if (arguments[i] > max) max = arguments[i];
// Return the biggest
return max;
}
var largest = max(1, 10, 100, 2, 3, 1000, 4, 5, 10000, 6); // => 10000
172 | Chapter 8: Functions
Documents you may be interested
Documents you may be interested