ECMAScript 6 (or ECMAScript 2015) is the newest version of the ECMAScript standard and has remarkably improved parameter handling in JavaScript. We can now use rest parameters, default values and destructuring, among other new features.
In this tutorial, we will explore arguments and parameters in detail and see how ECMAScript 6 has upgraded them.
Further Reading on SmashingMag:
- Writing Next Generation Reusable JavaScript Modules in ECMAScript 6
- ECMAScript 6 (ES6): What’s New In The Next Version Of JavaScript
- Making A Complete Polyfill For The HTML5 Details Element
- Generating SVG With React
Arguments Versus Parameters
Arguments and parameters are often referred to interchangeably. Nevertheless, for the purpose of this tutorial, we will make a distinction. In most standards, parameters (or formal parameters) are what’s given in the function declaration, and arguments (or actual parameters) are what’s passed to the function. Consider this function:
function foo(param1, param2) {
// do something
}
foo(10, 20);
In this function, param1
and param2
are function parameters, and the values passed to the function (10
and 20
) are arguments.
Spread Operator (…)
In ECMAScript 5, the apply()
method is a convenient tool for passing an array as arguments to a function. For example, it’s commonly used with the Math.max()
method to find the highest value in an array. Consider this code fragment:
var myArray = [5, 10, 50];
Math.max(myArray); // Error: NaN
Math.max.apply(Math, myArray); // 50
The Math.max()
method doesn’t support arrays; it accepts only numbers. When an array is passed to the Math.max()
function, it throws an error. But when the apply()
method is used, the array is sent as individual numbers, so the Math.max()
method can handle it.
Fortunately, with the introduction of the spread operator in ECMAScript 6, we no longer need to use the apply()
method. With the spread operator, we can easily expand an expression into multiple arguments:
var myArray = [5, 10, 50];
Math.max(...myArray); // 50
Here, the spread operator expands myArray
to create individual values for the function. While simulating the spread operator using apply()
in ECMAScript 5 is possible, the syntax is confusing and lacks the flexibility of the spread operator. The spread operator not only is easier to use, but packs more features. For example, it can be used multiple times and can be mixed with other arguments in a function
call:
function myFunction() {
for(var i in arguments){
console.log(arguments[i]);
}
}
var params = [10, 15];
myFunction(5, ...params, 20, ...[25]); // 5 10 15 20 25
Another advantage of the spread operator is that it can easily be used with constructors:
new Date(...[2016, 5, 6]); // Mon Jun 06 2016 00:00:00 GMT-0700 (Pacific Daylight Time)
Of course, we could rewrite the preceding code in ECMAScript 5, but we would need to use a complicated pattern to avoid getting a type error:
new Date.apply(null, [2016, 4, 24]); // TypeError: Date.apply is not a constructor
new (Function.prototype.bind.apply(Date, [null].concat([2016, 5, 6]))); // Mon Jun 06 2016 00:00:00 GMT-0700 (Pacific Daylight Time)
Spread Operator Browser Support in Function Calls
Desktop browsers:
Chrome | Firefox | Internet Explorer | Microsoft Edge | Opera | Safari |
---|---|---|---|---|---|
46 | 27 | – | Supported | – | 7.1 |
Mobile browsers:
Chrome for Android | Firefox Mobile | Safari Mobile | Opera Mobile | IE Mobile |
---|---|---|---|---|
46 | 27 | 8 | – | – |
Rest Parameters
The rest parameter has the same syntax as the spread operator, but instead of expanding an array into parameters, it collects parameters and turns them into an array.
function myFunction(...options) {
return options;
}
myFunction('a', 'b', 'c'); // ["a", "b", "c"]
If there are no arguments, the rest parameter will be set to an empty array:
function myFunction(...options) {
return options;
}
myFunction(); // []
A rest parameter is particularly useful when creating a variadic function (a function that accepts a variable number of arguments). Having the benefit of being arrays, rest parameters can readily replace the arguments
object (which we’ll explain later in this tutorial). Consider this function, written in ECMAScript 5:
function checkSubstrings(string) {
for (var i = 1; i < arguments.length; i++) {
if (string.indexOf(arguments[i]) === -1) {
return false;
}
}
return true;
}
checkSubstrings('this is a string', 'is', 'this'); // true
This function checks whether a string contains a number of substrings. The first problem with this function is that we have to look inside the function
’s body to see that it takes multiple arguments. The second problem is that the iteration must start from 1
instead of 0
, because arguments[0]
points to the first argument. If we later decide to add another parameter before or after the string, we might forget to update the loop. With the rest parameters, we easily avoid these problems:
function checkSubstrings(string, ...keys) {
for (var key of keys) {
if (string.indexOf(key) === -1) {
return false;
}
}
return true;
}
checkSubstrings('this is a string', 'is', 'this'); // true
The output of this function is the same as the previous one. Here again, the parameter string
is filled with the argument that is passed first, but the rest of the arguments are put in an array and assigned to the variable keys
.
Using the rest parameter instead of the arguments
object improves the readability of the code and avoids optimization issues in JavaScript. Nevertheless, the rest parameter is not without its limitations. For example, it must be the last argument; otherwise, a syntax error will occur:
function logArguments(a, ...params, b) {
console.log(a, params, b);
}
logArguments(5, 10, 15); // SyntaxError: parameter after rest parameter
Another limitation is that only one rest parameter is allowed in the function
declaration:
function logArguments(...param1, ...param2) {
}
logArguments(5, 10, 15); // SyntaxError: parameter after rest parameter
Rest Parameters Browser Support
Desktop browsers:
Chrome | Firefox | Internet Explorer | Microsoft Edge | Opera | Safari |
---|---|---|---|---|---|
47 | 15 | – | Supported | 34 | – |
Mobile browsers:
Chrome for Android | Firefox Mobile | Safari Mobile | Opera Mobile | IE Mobile |
---|---|---|---|---|
47 | 15 | – | – | – |
Default Parameters
Default Parameters in ECMAScript 5
JavaScript does not support default parameters in ECMAScript 5, but there is an easy workaround. Using a logical OR
operator (||
) inside the function, we can easily simulate default parameters in ECMAScript 5. Consider this function:
function foo(param1, param2) {
param1 = param1 || 10;
param2 = param2 || 10;
console.log(param1, param2);
}
foo(5, 5); // 5 5
foo(5); // 5 10
foo(); // 10 10
This function expects two arguments, but when it is called without arguments, it will use the default values. Inside the function, missing arguments are automatically set to undefined; so, we can detect these arguments and declare default values for them. To detect missing arguments and set default values, we use the logical OR
operator (||
). This operator examines its first argument: If it’s truthy, the operator returns it; if it’s not, the operator returns its second argument.
This approach is commonly used in functions, but it has a flaw. Passing 0
or null
will trigger a default value, too, because these are considered falsy values. So, if we actually need to pass 0
or null
to this function, we would need an alternate way to check whether an argument is missing:
function foo(param1, param2) {
if(param1 === undefined){
param1 = 10;
}
if(param2 === undefined){
param2 = 10;
}
console.log(param1, param2);
}
foo(0, null); // 0, null
foo(); // 10, 10
Inside this function, the types of passed arguments are checked to make sure they are undefined before default values are assigned. This approach requires just a bit more code, but it is a safer alternative and allows us to pass 0
and null
to the function.
Default Parameters in ECMAScript 6
With ECMAScript 6, we no longer need to check for undefined values to simulate default parameters. We can now put default values right in the function
declaration:
function foo(a = 10, b = 10) {
console.log(a, b);
}
foo(5); // 5 10
foo(0, null); // 0 null
As you can see, omitting an argument triggers the default value, but passing 0
or null
won’t. We can even use functions to retrieve values for default parameters:
function getParam() {
alert("getParam was called");
return 3;
}
function multiply(param1, param2 = getParam()) {
return param1 * param2;
}
multiply(2, 5); // 10
multiply(2); // 6 (also displays an alert dialog)
Note that the getParam
function is called only if the second argument is omitted. So, when we call the multiply()
function with two parameters, the alert won’t be displayed.
Another interesting feature of default parameters is that we are able to refer to other parameters and variables in the function
declaration:
function myFunction(a=10, b=a) {
console.log('a = ' + a + '; b = ' + b);
}
myFunction(); // a=10; b=10
myFunction(22); // a=22; b=22
myFunction(2, 4); // a=2; b=4
You can even perform operations in the function
declaration:
function myFunction(a, b = ++a, c = a*b) {
console.log(c);
}
myFunction(5); // 36
Note that, unlike some other languages, JavaScript evaluates default parameters at call time:
function add(value, array = []) {
array.push(value);
return array;
}
add(5); // [5]
add(6); // [6], not [5, 6]
Default Parameter Browser Support
Desktop browsers:
Feature | Chrome | Firefox | Internet Explorer | Microsoft Edge | Opera | Safari |
---|---|---|---|---|---|---|
Basic support | 49 | 15 | – | 14 | – | – |
Parameters without defaults after default parameter | 49 | 26 | – | 14 | – | – |
Mobile browsers:
Feature | Chrome for Android | Firefox Mobile | Safari Mobile | Opera Mobile | IE Mobile |
---|---|---|---|---|---|
Basic support | 49 | 15 | – | – | – |
Parameters without defaults after default parameter | 46 | 26 | – | – | – |
Destructuring
Destructuring is a new feature in ECMAScript 6 that enables us to extract values from arrays and object and to assign them to variables using a syntax that is similar to object and array literals. The syntax is clear and easy to understand and is particularly useful when passing arguments to a function.
In ECMAScript 5, a configuration object is often used to handle a large number of optional parameters, especially when the order of properties does not matter. Consider this function:
function initiateTransfer(options) {
var protocol = options.protocol,
port = options.port,
delay = options.delay,
retries = options.retries,
timeout = options.timeout,
log = options.log;
// code to initiate transfer
}
options = {
protocol: 'http',
port: 800,
delay: 150,
retries: 10,
timeout: 500,
log: true
};
initiateTransfer(options);
This pattern is commonly used by JavaScript developers, and it works well, but we have to look inside the function
body to see what parameters it expects. With destructured parameters, we can clearly indicate the parameters in the function
declaration:
function initiateTransfer({protocol, port, delay, retries, timeout, log}) {
// code to initiate transfer
};
var options = {
protocol: 'http',
port: 800,
delay: 150,
retries: 10,
timeout: 500,
log: true
}
initiateTransfer(options);
In this function, we’ve used an object destructuring pattern, instead of a configuration object. This makes our function not only more concise, but easier to read.
We can also combine destructured parameters with regular ones:
function initiateTransfer(param1, {protocol, port, delay, retries, timeout, log}) {
// code to initiate transfer
}
initiateTransfer('some value', options);
Note that a type error will be thrown if parameters are omitted in the function
call:
function initiateTransfer({protocol, port, delay, retries, timeout, log}) {
// code to initiate transfer
}
initiateTransfer(); // TypeError: Cannot match against 'undefined' or 'null'
This is the desired behavior when we need parameters to be required, but what if we want them to be optional? To prevent this error when parameters are missing, we need to assign a default value to destructured parameters:
function initiateTransfer({protocol, port, delay, retries, timeout, log} = {}) {
// code to initiate transfer
}
initiateTransfer(); // no error
In this function, an empty object is provided as the default value for the destructured parameters. Now, if this function is called without any parameters, no error will occur.
We can also assign a default value to each destructured parameter:
function initiateTransfer({
protocol = 'http',
port = 800,
delay = 150,
retries = 10,
timeout = 500,
log = true
}) {
// code to initiate transfer
}
In this example, every property has a default parameter, eliminating the need for us to manually check for undefined parameters and assign default values inside the function
body.
Destructuring Browser Support
Desktop browsers:
Feature | Chrome | Firefox | Internet Explorer | Microsoft Edge | Opera | Safari |
---|---|---|---|---|---|---|
Basic support | 49 | 2.0 | – | 14 | – | 7.1 |
Destructured parameter with default value assignment | 49 | 47 | – | 14 | – | – |
Mobile browsers:
Feature | Chrome for Android | Firefox Mobile | Safari Mobile | Opera Mobile | IE Mobile |
---|---|---|---|---|---|
Basic support | 49 | 1 | 8 | – | – |
Parameters without defaults after default parameter | 49 | 47 | – | – | – |
Passing Arguments
There are two ways to pass arguments to a function: by reference or by value. Modifying an argument that’s passed by reference is reflected globally, but modifying an argument that’s passed by value is reflected only inside the function.
In some languages, such as Visual Basic and PowerShell, we have the option to specify whether to pass an argument by reference or by value, but that’s not the case with JavaScript.
Passing Arguments by Value
Technically, JavaScript can only pass by value. When we pass an argument to a function by value, a copy of that value is created within the function
scope. Thus, any changes to the value are reflected only inside the function
. Consider this example:
var a = 5;
function increment(a) {
a = ++a;
console.log(a);
}
increment(a); // 6
console.log(a); // 5
Here, modifying the argument inside the function has no effect on the original value. So, when the variable is logged from outside the function, the printed value is still 5
.
Passing Arguments by Reference
In JavaScript, everything is passed by value, but when we pass a variable that refers to an object (including arrays), the “value” is a reference to the object, and changing a property of an object referenced by a variable does change the underlying object.
Consider this function:
function foo(param){
param.bar = 'new value';
}
obj = {
bar : 'value'
}
console.log(obj.bar); // value
foo(obj);
console.log(obj.bar); // new value
As you can see, the property of the object is modified inside the function, but the modified value is visible outside of the function.
When we pass a non-primitive value such as an array or object, behind the scene a variable is created that points to the location of the original object in memory. This variable is then passed to the function, and modifying it will affect the original object.
Type Checking And Missing Or Extra Parameters
In a strongly typed language, we have to specify the type of parameters in the function
declaration, but JavaScript lacks this feature. In JavaScript, it doesn’t matter what type of data or how many arguments we pass to a function.
Suppose we have a function that accepts only one argument. When we call that function, we are not limited to pass just one argument to the function; we are free to pass one, two or more arguments! We may even choose to pass nothing at all, and no errors will occur.
The number of arguments and parameters can differ in two ways:
- Fewer arguments than parameters. The missing parameters will equal
undefined
. - More arguments than parameters. The extra parameters will be ignored but can be retrieved via the special array-like variable arguments (discussed next).
Mandatory Arguments
If an argument is missing in a function
call, it will be set to undefined
. We can take advantage of this behavior and throw an error if an argument is omitted:
function foo(mandatory, optional) {
if (mandatory === undefined) {
throw new Error('Missing parameter: mandatory');
}
}
In ECMAScript 6, we can take this further and use default parameters to set mandatory arguments:
function throwError() {
throw new Error('Missing parameter');
}
function foo(param1 = throwError(), param2 = throwError()) {
// do something
}
foo(10, 20); // ok
foo(10); // Error: missing parameter
Arguments Object
The support for rest parameters was added to ECMAScript 4 with the intention of replacing the arguments
object, but ECMAScript 4 never came to fruition. With the release of ECMAScript 6, JavaScript now officially supports the rest parameters. It also nixed the plan to drop support for the arguments
object.
The arguments
object is an array-like object that is available within all functions. It allows the argument
’s values passed to the function to be retrieved by number, rather than by name. The object allows us to pass any number of arguments to a function. Consider the following code fragment:
function checkParams(param1) {
console.log(param1); // 2
console.log(arguments[0], arguments[1]); // 2 3
console.log(param1 + arguments[0]); // 2 + 2
}
checkParams(2, 3);
This function expects to receive only one argument. When we call it with two arguments, the first argument is accessible in the function by the parameter name param1
or the arguments object arguments[0]
, but the second argument is accessible only as arguments[1]
. Also, note that the arguments
object can be used in conjunction with named arguments.
The arguments
object contains an entry for each argument passed to the function, and the index of the first entry starts at 0
. If we wanted to access more arguments in the example above, we would write arguments[2]
, arguments[3]
and so on.
We could even skip setting named parameters altogether and just use the arguments
object:
function checkParams() {
console.log(arguments[1], arguments[0], arguments[2]);
}
checkParams(2, 4, 6); // 4 2 6
In fact, named parameters are a convenience, not a necessity. Similarly, the rest parameters can be used to reflect the passed arguments:
function checkParams(...params) {
console.log(params[1], params[0], params[2]); // 4 2 6
console.log(arguments[1], arguments[0], arguments[2]); // 4 2 6
}
checkParams(2, 4, 6);
The arguments
object is an array-like object, but it lacks array methods such as slice()
and foreach()
. In order to use array methods on the arguments
object, the object first needs to be converted into a real array:
function sort() {
var a = Array.prototype.slice.call(arguments);
return a.sort();
}
sort(40, 20, 50, 30); // [20, 30, 40, 50]
In this function, Array.prototype.slice.call()
is used as a quick way to convert the arguments
object into an array. Next, the sort()
method sorts the items of the array and returns it.
ECMAScript 6 has an even more straightforward way. Array.from()
, a new addition in ECMAScript 6, creates a new array from any array-like object:
function sort() {
var a = Array.from(arguments);
return a.sort();
}
sort(40, 20, 50, 30); // [20, 30, 40, 50]
The Length Property
Although the arguments object is not technically an array, it has a length
property that can be used to check the number of arguments passed to a function:
function countArguments() {
console.log(arguments.length);
}
countArguments(); // 0
countArguments(10, null, "string"); // 3
By using the length
property, we have a better control over the number of arguments passed to a function. For example, if a function requires two arguments to work, we could use the length
property to check the number of passed arguments, and throw an error if they are fewer than expected:
function foo(param1, param2) {
if (arguments.length < 2) {
throw new Error("This function expects at least two arguments");
} else if (arguments.length === 2) {
// do something
}
}
Rest parameters are arrays, so they have a length
property. In ECMAScript 6 the preceding code can be rewritten with rest parameters:
function foo(...params) {
if (params.length < 2) {
throw new Error("This function expects at least two arguments");
} else if (params.length === 2) {
// do something
}
}
The Callee And Caller Properties
The callee
property refers to the function that is currently running, and the caller
refers to the function that has called the currently executing function. In ECMAScript 5 strict mode, these properties are deprecated, and attempting to access them causes a TypeError.
The arguments.callee
property is useful in recursive functions (a recursive function is a regular function that refers to itself by its name), especially when the function name is not available (an anonymous function). Because an anonymous function doesn’t have a name, the only way to refer to it is by arguments.callee
.
var result = (function(n) {
if (n <= 1) {
return 1;
} else {
return n * arguments.callee(n - 1);
}
})(4); // 24
Arguments Object In Strict And Non-Strict Modes
In ECMAScript 5 non-strict mode, the arguments
object has an unusual feature: It keeps its values in sync with the values of the corresponding named parameters.
Consider the following code fragment:
function foo(param) {
console.log(param === arguments[0]); // true
arguments[0] = 500;
console.log(param === arguments[0]); // true
return param
}
foo(200); // 500
Inside this function, a new value is assigned to arguments[0]
. Because arguments
’ values always stay in sync with the values of named parameters, the change to arguments[0]
will also change the value of param
. In fact, they are like two different names for the same variable. In ECMAScript 5 strict mode, this confusing behavior of the arguments
object has been removed:
"use strict";
function foo(param) {
console.log(param === arguments[0]); // true
arguments[0] = 500;
console.log(param === arguments[0]); // false
return param
}
foo(200); // 200
This time, changing arguments[0]
doesn’t affect param
, and the output is as expected. The output of this function in ECMAScript 6 is the same as in ECMAScript 5 strict mode, but keep in mind that when default values are used in the function
declaration, the arguments
object is not affected:
function foo(param1, param2 = 10, param3 = 20) {
console.log(param1 === arguments[0]); // true
console.log(param2 === arguments[1]); // true
console.log(param3 === arguments[2]); // false
console.log(arguments[2]); // undefined
console.log(param3); // 20
}
foo('string1', 'string2');
In this function, even though param3
has a default value, it’s not equal to arguments[2]
because only two argument are passed to the function. In other words, setting default values has no effect on the arguments
object.
Conclusion
ECMAScript 6 has brought hundreds of small and big improvements to JavaScript. More and more, developers are using ECMAScript 6 features, and soon these features will be unavoidable. In this tutorial, we’ve learned how ECMAScript 6 has upgraded parameter handling in JavaScript, but we’ve just scratched the surface of ECMAScript 6. A lot of other new and interesting features of the language are worth checking out.
Links
- ECMAScript 6 Compatibility Table, Juriy Zaytsev
- “ECMAScript 2015 Language Specification,” ECMA International
Front page image credits: JavaScript Planet.