loading...

JavaScript – Function Arguments and Parameters

JavaScript function definitions do not specify an expected type
for the function parameters, 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 arguments 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.

Optional Parameters

When a function is invoked with fewer arguments than declared
parameters, the additional 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 Logical OR (||) 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 second:
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.

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 Array-Like Objects) 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. JavaScript’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 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 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 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. See Array-Like Objects for
more on array-like objects.

The Arguments object has one very unusual
feature. In non-strict mode, when a function 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 Arguments 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 argument 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.

The callee and caller properties

In addition to its array elements, the Arguments object
defines callee and caller properties. In ECMAScript 5
strict mode, these properties are guaranteed to raise a TypeError
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:

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

Using Object Properties As Arguments

When a function has more than three parameters, it becomes
difficult for the programmer 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});

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 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 Type Conversions, 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 Array-Like Objects:

// 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) {
    if (isArrayLike(a)) {
        var total = 0;
        for(var i = 0; i < a.length; i++) {  // Loop though all elements
            var element = a[i];
            if (element == null) continue;   // Skip null and undefined
            if (isFinite(element)) total += element;
            else throw new Error("sum(): elements must be finite numbers");
        }
        return total;
    }
    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 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], n;
        if (element == null) continue;  // Ignore null and undefined arguments

        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;  
}

Comments are closed.

loading...