Chapter 15. Functions
Table of contents
Buy the book
(Ad, please don’t block.)

Chapter 15. Functions

Functions are values that can be called. One way of defining a function is called a function declaration. For example, the following code defines the function id that has a single parameter, x:

function id(x) {
    return x;
}

The return statement returns a value from id. You can call a function by mentioning its name, followed by arguments in parentheses:

> id('hello')
'hello'

If you don’t return anything from a function, undefined is returned (implicitly):

> function f() { }
> f()
undefined

This section showed just one way of defining and one way of calling a function. Others are described later.

The Three Roles of Functions in JavaScript

Once you have defined a function as just shown, it can play several roles:

Nonmethod function (“normal function”)

You can call a function directly. Then it works as a normal function. Here’s an example invocation:

id('hello')

By convention, the names of normal functions start with lowercase letters.

Constructor

You can invoke a function via the new operator. Then it becomes a constructor, a factory for objects. Here’s an example invocation:

new Date()

By convention, the names of constructors start with uppercase letters.

Method

You can store a function in a property of an object, which turns it into a method that you can invoke via that object. Here’s an example invocation:

obj.method()

By convention, the names of methods start with lowercase letters.

Nonmethod functions are explained in this chapter; constructors and methods are explained in Chapter 17.

Defining Functions

This section describes three ways to create a function:

  • Via a function expression
  • Via a function declaration
  • Via the constructor Function()

All functions are objects, instances of Function:

function id(x) {
    return x;
}
console.log(id instanceof Function); // true

Therefore, functions get their methods from Function.prototype.

Function Expressions

A function expression produces a value—a function object. For example:

var add = function (x, y) { return x + y };
console.log(add(2, 3)); // 5

The preceding code assigned the result of a function expression to the variable add and called it via that variable. The value produced by a function expression can be assigned to a variable (as shown in the last example), passed as an argument to another function, and more. Because normal function expressions don’t have a name, they are also called anonymous function expressions.

Named function expressions

You can give a function expression a name. Named function expressions allow a function expression to refer to itself, which is useful for self-recursion:

var fac = function me(n) {
    if (n > 0) {
        return n * me(n-1);
    } else {
        return 1;
    }
};
console.log(fac(3)); // 6

Note

The name of a named function expression is only accessible inside the function expression:

var repeat = function me(n, str) {
    return n > 0 ? str + me(n-1, str) : '';
};
console.log(repeat(3, 'Yeah')); // YeahYeahYeah
console.log(me); // ReferenceError: me is not defined

Function Declarations

The following is a function declaration:

function add(x, y) {
    return x + y;
}

The preceding looks like a function expression, but it is a statement (see Expressions Versus Statements). It is roughly equivalent to the following code:

var add = function (x, y) {
    return x + y;
};

In other words, a function declaration declares a new variable, creates a function object, and assigns it to the variable.

The Function Constructor

The constructor Function() evaluates JavaScript code stored in strings. For example, the following code is equivalent to the previous example:

var add = new Function('x', 'y', 'return x + y');

However, this way of defining a function is slow and keeps code in strings (inaccessible to tools). Therefore, it is much better to use a function expression or a function declaration if possible. Evaluating Code Using new Function() explains Function() in more detail; it works similarly to eval().

Hoisting

Hoisting means “moving to the beginning of a scope.” Function declarations are hoisted completely, variable declarations only partially.

Function declarations are completely hoisted. That allows you to call a function before it has been declared:

foo();
function foo() {  // this function is hoisted
    ...
}

The reason the preceding code works is that JavaScript engines move the declaration of foo to the beginning of the scope. They execute the code as if it looked like this:

function foo() {
    ...
}
foo();

var declarations are hoisted, too, but only the declarations, not assignments made with them. Therefore, using a var declaration and a function expression similarly to the previous example results in an error:

foo();  // TypeError: undefined is not a function
var foo = function () {
    ...
};

Only the variable declaration is hoisted. The engine executes the preceding code as:

var foo;
foo();  // TypeError: undefined is not a function
foo = function () {
    ...
};

The Name of a Function

Most JavaScript engines support the nonstandard property name for function objects. Function declarations have it:

> function f1() {}
> f1.name
'f1'

The name of anonymous function expressions is the empty string:

> var f2 = function () {};
> f2.name
''

Named function expressions, however, do have a name:

> var f3 = function myName() {};
> f3.name
'myName'

The name of a function is useful for debugging. Some people always give their function expressions names for that reason.

Which Is Better: A Function Declaration or a Function Expression?

Should you prefer a function declaration like the following?

function id(x) {
    return x;
}

Or the equivalent combination of a var declaration plus a function expression?

var id = function (x) {
    return x;
};

They are basically the same, but function declarations have two advantages over function expressions:

  • They are hoisted (see Hoisting), so you can call them before they appear in the source code.
  • They have a name (see The Name of a Function). However, JavaScript engines are getting better at inferring the names of anonymous function expressions.

More Control over Function Calls: call(), apply(), and bind()

call(), apply(), and bind() are methods that all functions have (remember that functions are objects and therefore have methods). They can supply a value for this when invoking a method and thus are mainly interesting in an object-oriented context (see Calling Functions While Setting this: call(), apply(), and bind()). This section explains two use cases for nonmethods.

func.apply(thisValue, argArray)

This method uses the elements of argArray as arguments while calling the function func; that is, the following two expressions are equivalent:

func(arg1, arg2, arg3)
func.apply(null, [arg1, arg2, arg3])

thisValue is the value that this has while executing func. It is not needed in a non-object-oriented setting and is thus null here.

apply() is useful whenever a function accepts multiple arguments in an array-like manner, but not an array.

Thanks to apply(), we can use Math.max() (see Other Functions) to determine the maximum element of an array:

> Math.max(17, 33, 2)
33
> Math.max.apply(null, [17, 33, 2])
33

func.bind(thisValue, arg1, ..., argN)

This performs partial function application—a new function is created that calls func with this set to thisValue and the following arguments: first arg1 until argN, and then the actual arguments of the new function. thisValue is not needed in the following non-object-oriented setting, which is why it is null.

Here, we use bind() to create a new function plus1() that is like add(), but only requires the parameter y, because x is always 1:

function add(x, y) {
    return x + y;
}
var plus1 = add.bind(null, 1);
console.log(plus1(5));  // 6

In other words, we have created a new function that is equivalent to the following code:

function plus1(y) {
    return add(1, y);
}

Handling Missing or Extra Parameters

JavaScript does not enforce a function’s arity: you can call it with any number of actual parameters, independent of what formal parameters have been defined. Hence, the number of actual parameters and formal parameters can differ in two ways:

More actual parameters than formal parameters
The extra parameters are ignored but can be retrieved via the special array-like variable arguments (discussed momentarily).
Fewer actual parameters than formal parameters
The missing formal parameters all have the value undefined.

All Parameters by Index: The Special Variable arguments

The special variable arguments exists only inside functions (including methods). It is an array-like object that holds all of the actual parameters of the current function call. The following code uses it:

function logArgs() {
    for (var i=0; i<arguments.length; i++) {
        console.log(i+'. '+arguments[i]);
    }
}

And here is the interaction:

> logArgs('hello', 'world')
0. hello
1. world

arguments has the following characteristics:

Deprecated features of arguments

Strict mode drops several of the more unusual features of arguments:

  • arguments.callee refers to the current function. It is mainly used to do self-recursion in anonymous functions, and is not allowed in strict mode. As a workaround, use a named function expression (see Named function expressions), which can refer to itself via its name.
  • In nonstrict mode, arguments stays up-to-date if you change a parameter:

    function sloppyFunc(param) {
        param = 'changed';
        return arguments[0];
    }
    console.log(sloppyFunc('value'));  // changed

    But this kind of updating is not done in strict mode:

    function strictFunc(param) {
        'use strict';
        param = 'changed';
        return arguments[0];
    }
    console.log(strictFunc('value'));  // value
  • Strict mode forbids assigning to the variable arguments (e.g., via arguments++). Assigning to elements and properties is still allowed.

Mandatory Parameters, Enforcing a Minimum Arity

There are three ways to find out whether a parameter is missing. First, you can check if it is undefined:

function foo(mandatory, optional) {
    if (mandatory === undefined) {
        throw new Error('Missing parameter: mandatory');
    }
}

Second, you can interpret the parameter as a boolean. Then undefined is considered false. However, there is a caveat: several other values are also considered false (see Truthy and Falsy Values), so the check cannot distinguish between, say, 0 and a missing parameter:

if (!mandatory) {
    throw new Error('Missing parameter: mandatory');
}

Third, you can also check the length of arguments to enforce a minimum arity:

if (arguments.length < 1) {
    throw new Error('You need to provide at least 1 argument');
}

The last approach differs from the other ones:

  • The first two approaches don’t distinguish between foo() and foo(undefined). In both cases, an exception is thrown.
  • The third approach throws an exception for foo() and sets optional to undefined for foo(undefined).

Optional Parameters

If a parameter is optional, it means that you give it a default value if it is missing. Similarly to mandatory parameters, there are four alternatives.

First, check for undefined:

function bar(arg1, arg2, optional) {
    if (optional === undefined) {
        optional = 'default value';
    }
}

Second, interpret optional as a boolean:

if (!optional) {
    optional = 'default value';
}

Third, you can use the Or operator || (see Logical Or (||)), which returns the left operand, if it isn’t falsy. Otherwise, it returns the right operand:

// Or operator: use left operand if it isn't falsy
optional = optional || 'default value';

Fourth, you can check a function’s arity via arguments.length:

if (arguments.length < 3) {
    optional = 'default value';
}

Again, the last approach differs from the other ones:

  • The first three approaches don’t distinguish between bar(1, 2) and bar(1, 2, undefined). In both cases, optional is 'default value'.
  • The fourth approach sets optional to 'default value' for bar(1, 2) and leaves it undefined (i.e., unchanged) for bar(1, 2, undefined).

Another possibility is to hand in optional parameters as named parameters, as properties of an object literal (see Named Parameters).

Simulating Pass-by-Reference Parameters

In JavaScript, you cannot pass parameters by reference; that is, if you pass a variable to a function, its value is copied and handed to the function (pass by value). Therefore, the function can’t change the variable. If you need to do so, you must wrap the value of the variable (e.g., in an array).

This example demonstates a function that increments a variable:

function incRef(numberRef) {
    numberRef[0]++;
}
var n = [7];
incRef(n);
console.log(n[0]);  // 8

Pitfall: Unexpected Optional Parameters

If you hand a function c as a parameter to another function f, then you have to be aware of two signatures:

  • The signature that f expects its parameter to have. f might provide several parameters, and c can decide how many (if any) of them to use.
  • The actual signature of c. For example, it might support optional parameters.

If the two diverge, then you can get unexpected results: c could have optional parameters that you don’t know about and that would interpret additional arguments provided by f incorrectly.

As an example, consider the array method map() (see Transformation Methods) whose parameter is normally a function with a single parameter:

> [ 1, 2, 3 ].map(function (x) { return x * x })
[ 1, 4, 9 ]

One function that you could pass as an argument is parseInt() (see Integers via parseInt()):

> parseInt('1024')
1024

You may (incorrectly) think that map() provides only a single argument and that parseInt() accepts only a single argument. Then you would be surprised by the following result:

> [ '1', '2', '3' ].map(parseInt)
[ 1, NaN, NaN ]

map() expects a function with the following signature:

function (element, index, array)

But parseInt() has the following signature:

parseInt(string, radix?)

Thus, map() not only fills in string (via element), but also radix (via index). That means that the values of the preceding array are produced as follows:

> parseInt('1', 0)
1
> parseInt('2', 1)
NaN
> parseInt('3', 2)
NaN

To sum up, be careful with functions and methods whose signature you are not sure about. If you use them, it often makes sense to be explicit about what parameters are received and what parameters are passed on. That is achieved via a callback:

> ['1', '2', '3'].map(function (x) { return parseInt(x, 10) })
[ 1, 2, 3 ]

Named Parameters

When calling a function (or method) in a programming language, you must map the actual parameters (specified by the caller) to the formal parameters (of a function definition). There are two common ways to do so:

Named parameters have two main benefits: they provide descriptions for arguments in function calls and they work well for optional parameters. I’ll first explain the benefits and then show you how to simulate named parameters in JavaScript via object literals.

Named Parameters as Descriptions

As soon as a function has more than one parameter, you might get confused about what each parameter is used for. For example, let’s say you have a function, selectEntries(), that returns entries from a database. Given the following function call:

selectEntries(3, 20, 2);

what do these three numbers mean? Python supports named parameters, and they make it easy to figure out what is going on:

selectEntries(start=3, end=20, step=2)  # Python syntax

Simulating Named Parameters in JavaScript

JavaScript does not have native support for named parameters like Python and many other languages. But there is a reasonably elegant simulation: name parameters via an object literal, passed as a single actual parameter. When you use this technique, an invocation of selectEntries() looks like:

selectEntries({ start: 3, end: 20, step: 2 });

The function receives an object with the properties start, end, and step. You can omit any of them:

selectEntries({ step: 2 });
selectEntries({ end: 20, start: 3 });
selectEntries();

You could implement selectEntries() as follows:

function selectEntries(options) {
    options = options || {};
    var start = options.start || 0;
    var end = options.end || getDbLength();
    var step = options.step || 1;
    ...
}

You can also combine positional parameters with named parameters. It is customary for the latter to come last:

someFunc(posArg1, posArg2, { namedArg1: 7, namedArg2: true });

Note

In JavaScript, the pattern for named parameters shown here is sometimes called options or option object (e.g., by the jQuery documentation).

Next: 16. Variables: Scopes, Environments, and Closures