Functions are values that can be called. One way of defining a function is called a function declaration.
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 .
Terminology: “Parameter” Versus “Argument”
The terms parameter and argument are
- Parameters are used to define a function. They are also called formal parameters and formal arguments. In the following example,
param1
andparam2
are parameters:
function
foo
(
param1
,
param2
)
{
...
}
- Arguments are used to invoke a function. They are also called actual parameters and actual arguments. In the following example,
3
and7
are arguments:
foo
(
3
,
7
);
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
.
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 ). It is roughly equivalent to the following code:
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. 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:
The Name of a Function
- > function f1() {}
- > f1.name
- 'f1'
The name of anonymous function expressions
- > 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.
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 ), 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 ). 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 ) to determine the maximum element of an array:
- > Math.max(17, 33, 2)
- 33
- > Math.max.apply(null, [17, 33, 2])
- 33
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
- 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:
- It is array-like, but not an array. On one hand, it has a property
length
, and individual parameters can be read and written by index.
On the other hand, arguments
is not an array, it is only similar to one. It has none of the array methods (slice()
, forEach()
, etc.). Thankfully, you can borrow array methods or convert arguments
to an array, as explained in .
- It is an object, so all object methods and operators are available. For example, you can use the
in
operator (Iteration and Detection of Properties) to check whetherarguments
“has” a given index:
- > function f() { return 1 in arguments }
- > f('a')
- false
- > f('a', 'b')
- true
You can use hasOwnProperty()
() in a similar manner:
- > function g() { return arguments.hasOwnProperty(1) }
- > g('a', 'b')
- true
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 ), which can refer to itself via its name.- In nonstrict mode,
arguments
stays up-to-date if you change a parameter:
function
sloppyFunc
(
param
)
{
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., viaarguments++
). Assigning to elements and properties is still allowed.
Mandatory Parameters, Enforcing a Minimum Arity
There are three 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 (see ), 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:
The last approach differs from the other ones:
- The first two approaches don’t distinguish between
foo()
andfoo(undefined)
. In both cases, an exception is thrown. - The third approach throws an exception for
foo()
and setsoptional
toundefined
forfoo(undefined)
.
Optional Parameters
If a parameter is optional, it means that you give it a default value if it is missing.
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 ), 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)
andbar(1, 2, undefined)
. In both cases,optional
is'default value'
. - The fourth approach sets
optional
to'default value'
forbar(1, 2)
and leaves itundefined
(i.e., unchanged) forbar(1, 2, undefined)
.
Another possibility is to hand in optional parameters as named parameters, as properties of an object literal (see ).
Simulating Pass-by-Reference Parameters
This example demonstates a function that increments a variable:
function
incRef
(
numberRef
)
{
numberRef
[
0
]
++
;
}
var
n
=
[
7
];
incRef
(
n
);
console
.
log
(
n
[
0
]);
// 8
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, andc
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 ) 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 ):
- > 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 ]
- Positional parameters are mapped by position. The first actual parameter is mapped to the first formal parameter, the second actual to the second formal, and so on.
- Named parameters use names (labels) to perform the mapping. Names are associated with formal parameters in a function definition and label actual parameters in a function call. It does not matter in which order named parameters appear, as long as they are correctly labeled.
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
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
Optional Named Parameters
# Python syntax
selectEntries
(
step
=
2
)
selectEntries
(
end
=
20
,
start
=
3
)
selectEntries
()
Simulating Named Parameters in JavaScript
JavaScript does not have native support for named parameters like Python and many other languages.
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:
You can also combine positional parameters with named parameters. It is customary for the latter to come last:
Note
In JavaScript, the pattern for named parameters shown here is sometimes called options or option object (e.g., by the jQuery documentation).