• 沒有找到結果。

Function Arguments and Parameters

在文檔中 JavaScript: The Definitive Guide (頁 189-194)

Method Chaining

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

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

Functions like this one that can accept any number of arguments are called variadic functions, variable arity functions, or varargs functions. This book uses the most collo-quial term, varargs, which dates to the early days of the C programming language.

Note that varargs functions need not allow invocations with zero arguments. It is per-fectly reasonable to use the arguments[] object to write functions that expect some fixed number of named and required arguments followed by an arbitrary number of un-named optional arguments.

Remember that arguments is not really an array; it is an Arguments object. Each Argu-ments object defines numbered array eleArgu-ments and a length property, but it is not technically an array; it is better to think of it as an object that happens to have some numbered properties. See §7.11 for more on array-like objects.

The Arguments object has one very unusual feature. In non-strict mode, when a func-tion has named parameters, the array elements of the Arguments object are aliases for the parameters that hold the function arguments. The numbered elements of the Ar-guments object and the parameter names are like two different names for the same variable. Changing the value of an argument with an argument name changes the value that is retrieved through the arguments[] array. Conversely, changing the value of an argument through the arguments[] array changes the value that is retrieved by the ar-gument name. Here is an example that clarifies this:

function f(x) {

console.log(x); // Displays the initial value of the argument arguments[0] = null; // Changing the array element also changes x!

console.log(x); // Now displays "null"

}

This is emphatically not the behavior you would see if the Arguments object were an ordinary array. In that case, arguments[0] and x could refer initially to the same value, but a change to one would have no effect on the other.

This special behavior of the Arguments object has been removed in the strict mode of ECMAScript 5. There are other strict-mode differences as well. In non-strict functions, arguments is just an identifier. In strict mode, it is effectively a reserved word. Strict-mode functions cannot use arguments as a parameter name or as a local variable name, and they cannot assign values to arguments.

8.3.2.1 The callee and caller properties

In addition to its array elements, the Arguments object defines callee and caller prop-erties. In ECMAScript 5 strict mode, these properties are guaranteed to raise a Type-Error if you try to read or write them. Outside of strict mode, however, the ECMAScript standard says that the callee property refers to the currently running function.

caller is a nonstandard but commonly implemented property that refers to the function that called this one. The caller property gives access to the call stack, and the callee property is occasionally useful to allow unnamed functions to call themselves recursively:

8.3 Function Arguments and Parameters | 173

Core JavaScript

var factorial = function(x) { if (x <= 1) return 1;

return x * arguments.callee(x-1);

};

8.3.3 Using Object Properties As Arguments

When a function has more than three parameters, it becomes difficult for the pro-grammer who invokes the function to remember the correct order in which to pass arguments. To save the programmer the trouble of consulting the documentation each time she uses the function, it can be nice to allow arguments to be passed as name/

value pairs in any order. To implement this style of method invocation, define your function to expect a single object as its argument and then have users of the function pass an object that defines the required name/value pairs. The following code gives an example and also demonstrates that this style of function invocation allows the function to specify defaults for any arguments that are omitted:

// Copy length elements of the array from to the array to.

// Begin copying with element from_start in the from array // and copy that element to to_start in the to array.

// It is hard to remember the order of the arguments.

function arraycopy(/* array */ from, /* index */ from_start, /* array */ to, /* index */ to_start, /* integer */ length)

{

// code goes here }

// This version is a little less efficient, but you don't have to // remember the order of the arguments, and from_start and to_start // default to 0.

function easycopy(args) { arraycopy(args.from,

args.from_start || 0, // Note default value provided args.to,

args.to_start || 0, args.length);

}

// Here is how you might invoke easycopy():

var a = [1,2,3,4], b = [];

easycopy({from: a, to: b, length: 4});

8.3.4 Argument Types

JavaScript method parameters have no declared types, and no type checking is performed on the values you pass to a function. You can help to make your code self-documenting by choosing descriptive names for function arguments and also by in-cluding argument types in comments, as in the arraycopy() method just shown. For arguments that are optional, you can include the word “optional” in the comment. And when a method can accept any number of arguments, you can use an ellipsis:

function max(/* number... */) { /* code here */ }

As described in §3.8, JavaScript performs liberal type conversion as needed. So if you write a function that expects a string argument and then call that function with a value of some other type, the value you passed will simply be converted to a string when the function tries to use it as a string. All primitive types can be converted to strings, and all objects have toString() methods (if not necessarily useful ones), so an error never occurs in this case.

This is not always true, however. Consider again the arraycopy() method shown earlier.

It expects an array as its first argument. Any plausible implementation will fail if that first argument is anything but an array (or possibly an array-like object). Unless you are writing a “throwaway” function that will be called only once or twice, it may be worth adding code to check the types of arguments like this. It is better for a function to fail immediately and predictably when passed bad values than to begin executing and fail later with an error message that is likely to be unclear. Here is an example function that performs type-checking. Note that it uses the isArrayLike() function from §7.11:

// Return the sum of the elements of array (or array-like object) a.

// The elements of a must all be numbers or null and undefined are ignored.

function sum(a) {

else throw new Error("sum(): argument must be array-like");

}

This sum() method is fairly strict about the argument it accepts and throws suitably informative errors if it is passed bad values. It does offer a bit of flexibility, however, by working with array-like objects as well as true arrays and by ignoring null and undefined array elements.

JavaScript is a very flexible and loosely typed language, and sometimes it is appropriate to write functions that are flexible about the number and type of arguments they are passed. The following flexisum() method takes this approach (probably to an ex-treme). For example, it accepts any number of arguments but recursively processes any arguments that are arrays. In this way, it can be used as a varargs method or with an array argument. Furthermore, it tries its best to convert nonnumeric values to numbers before throwing an error:

8.3 Function Arguments and Parameters | 175

Core JavaScript

if (isArray(element)) // If the argument is an array n = flexisum.apply(this, element); // compute its sum recursively else if (typeof element === "function") // Else if it's a function...

n = Number(element()); // invoke it and convert.

else n = Number(element); // Else try to convert it if (isNaN(n)) // If we couldn't convert to a number, throw an error throw Error("flexisum(): can't convert " + element + " to number");

total += n; // Otherwise, add n to the total }

return total;

}

在文檔中 JavaScript: The Definitive Guide (頁 189-194)