Section 8.2. Function Arguments


8.2. Function Arguments

JavaScript functions can be invoked with any number of arguments, regardless of the number of arguments named in the function definition. Because a function is loosely typed, there is no way for it to declare the type of arguments it expects, and it is legal to pass values of any type to any function. The following subsections discuss these issues.

8.2.1. Optional Arguments

When a function is invoked with fewer arguments than are declared, the additional arguments have 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 arguments that are omitted (or specified as null). 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 or null, create and return // a new array function copyPropertyNamesToArray(o, /* optional */ a) {     if (!a) a = [];  // If undefined or null, use a blank array     for(var property in o) a.push(property);     return a; } 

With the function defined this way, you have flexibility in how it is invoked:

 // Get property names of objects o and p var a = copyPropertyNamesToArray(o); // Get o's properties into a new array copyPropertyNamesToArray(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 Chapter 5 that the || operator returns its first argument if that argument is true or a value that converts to TRue. Otherwise it returns its second argument. In this case, it returns a if a is defined and non-null, even if a is empty. Otherwise, it returns a new, empty array.

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 second, for example. In this case, he would have to explicitly pass undefined or null as the first argument.

8.2.2. Variable-Length Argument Lists: The Arguments Object

Within the body of a function, the identifier arguments has special meaning. arguments is a special property that refers to an object known as the Arguments object. The Arguments object is an array-like object (see Section 7.8) that allows the argument values passed to the function to be retrieved by number, rather than by name. The Arguments object also defines an additional callee property, described in the next section.

Although a JavaScript function is defined with a fixed number of named arguments, it can be passed any number of arguments when it is invoked. The Arguments object allows full access to these argument values, even when some or all are unnamed. 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 correct 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... } 

The arguments object also opens up an important possibility for JavaScript functions: they can be written so that they work with any number of arguments. Here's an example that shows how you can write a simple max() function that accepts any number of arguments 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 m = Number.NEGATIVE_INFINITY;     // Loop through all the arguments,   looking for, and     // remembering, the biggest     for(var i = 0; i < arguments.length; i++)         if (arguments[i] > m) m = arguments[i];     // Return the biggest     return m; } var largest = max(1, 10, 100, 2, 3, 1000, 4, 5, 10000, 6); 

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 colloquial 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 perfectly 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 unnamed optional arguments.

Remember that arguments is not really an array; it is an Arguments object. Each Arguments object defines numbered array elements 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. The ECMAScript specification does not require the Arguments object to implement any of the special behavior that arrays do. Although you can assign a value to the arguments.length property, for example, ECMAScript does not actually alter the number of array elements defined in the object. (See Section 7.6.3 for an explanation of the special behavior of the length property of true Array objects.)

The Arguments object has one very unusual feature. When a function has named arguments, the array elements of the Arguments object are synonyms for the local variables that hold the function arguments. The arguments[] array and the named arguments are two different ways of referring to 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 argument name. For example:

 function f(x) {     print(x);             // Displays the initial value of the argument     arguments[0] = null;  // Changing the array element also changes x!     print(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 reference would have no effect on the other reference.

Finally, bear in mind that arguments is just an ordinary JavaScript identifier, not a reserved word. If a function has an argument or local variable with that name, it hides the reference to the Arguments object. For this reason, it is a good idea to treat arguments as a reserved word and avoid using it as a variable name.

8.2.2.1. The callee Property

In addition to its array elements, the Arguments object defines a callee property that refers to the function that is currently being executed. This property is rarely useful, but it can be used to allow unnamed functions to invoke themselves recursively. For instance, here is an unnamed function literal that computes factorials:

 function(x) {     if (x <= 1) return 1;     return x * arguments.callee(x-1); } 

8.2.3. Using Object Properties as Arguments

When a function requires more than about three arguments, it becomes difficult for the programmer who invokes the function to remember the correct order in which to pass arguments. In order to save the programmer the trouble of consulting the documentation each time she uses the function, it would 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 literal 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]; var b = new Array(4); easycopy({from: a, to: b, length: 4}); 

8.2.4. Argument Types

Since JavaScript is loosely typed, method arguments 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 including 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 Chapter 3, 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 the case, 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 is worth adding code to check the types of arguments like this. If arguments of the wrong type are passed, throw an exception that reports the fact. It is better for a function to fail immediately and predictably when passed bad data than to begin executing and fail later when you try to access an array element of a number, for example, as in this code snippet:

 // Return the sum of the elements of array (or array-like object) a. // The elements of a must all be numbers, but null and undefined // elements are ignored. function sum(a) {     if ((a instanceof Array) ||                         // if array         (a && typeof a == "object" && "length" in a)) { // or array like         var total = 0;         for(var i = 0; i < a.length; i++) {             var element = a[i];             if (!element) continue;  // ignore null and undefined elements             if (typeof element == "number") total += element;             else throw new Error("sum(): all array elements must be numbers");         }         return total;     }     else throw new Error("sum(): argument must be an array"); } 

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 (and arguably takes it to an extreme). 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:

 function flexisum(a) {     var total = 0;     for(var i = 0; i < arguments.length; i++) {         var element = arguments[i];         if (!element) continue;  // Ignore null and undefined arguments         // Try to convert the argument to a number n,         // based on its type         var n;         switch(typeof element) {         case "number":             n = element;                  // No conversion needed here             break;         case "object":             if (element instanceof Array) // Recurse for arrays                 n = flexisum.apply(this, element);             else n = element.valueOf();   // valueOf method for other objects             break;         case "function":             n = element();                // Try to invoke functions             break;         case "string":             n = parseFloat(element);      // Try to parse strings             break;         case "boolean":             n = NaN;                      // Can't convert boolean values             break;         }         // If we got a valid number, add it to the total.         if (typeof n == "number" && !isNaN(n)) total += n;         // Otherwise report an error         else throw new Error("sum(): can't convert " + element + " to number");     }     return total; } 




JavaScript. The Definitive Guide
JavaScript: The Definitive Guide
ISBN: 0596101996
EAN: 2147483647
Year: 2004
Pages: 767

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net