Function - ES6
Summary
Generally, there'no fundamental changes in ES6 in terms of function
but a few minor improvements like parameters, introduction of arrow functions etc.
Default Parameters
In ES5, default parameters are not real supported, but we can use some tricks to mimic the behavior, for example:
function func(url, timeout, callback) {
timeout = timeout || 1000;
callback = callback || function() {};
// the rest of the function.
}
There are two major flaws for this pattern: 1) the code looks clumsy 2) what if the timeout
value is 0?
In ES6, default parameters are natively supported, see following examples:
function func(url, timeout=2000, callback=function() {}) {
// the rest of the code
}
In this example, url
will be considered as a required parameter, timeout
and callback
which have a default value are considered optional. In this way, we don't need any tricks to mimic default parameter behaviors.
Unlike other languages, e.g. Python, you don't have to specify default values for all tailing parameters, for example:
function func(url, timeout=2000, callback) {
// it's not a good part of Javascript IMO
}
How Default Parameters Affect arguments Object
In ES5 non-strict
and strict mode
, the arguments
object have different behaviors in terms of if it will be updated inside the function body. For example:
function nonStrictFunc(first, second) {
second = "updated";
console.log(first === arguments[0]); // true
console.log(second === arguments[1]); // true
}
function strictFunc(first, second) {
'use strict';
second = "updated";
console.log(first === arguments[0]); // true
console.log(second === arguments[1]); // false
}
nonStrictFunc('a', 'b');
strictFunc('a', 'b');
From this example, we can see that, in strict
mode, the arguments
object will remain unchanged inside of the function body.
In ES6, this behavior has been unified to be the same as it is in ES5 strict
mode. Even with default parameters given, the arguments
object will remain unchanged, see the example below:
function func(first, second='updated') {
console.log(arguments.length); // 1
console.log(first === arguments[0]); // true
console.log(second === arguments[1]); // false
}
Default Parameter Expression
One interesting feature added in ES6 is now you can now use a function expression to provide default parameter values, for example:
function getValue() {
return 1;
}
function func(first, second=getValue()) {
console.log(second); // 1
}
func('a');
Preceding arugments can be passed into the function expression to get the default value, but unseen parameters can not be used because they are still in TDZ
, for example:
function getValue(val) {
return val + 5;
}
function func1(first, second=getValue(first)) {
console.log(first); // 1
console.log(second); // 6
}
function func2(first=getValue(second), second) {
console.log(first);
console.log(second);
}
func1(1);
func2(undefined, 1); // ReferenceError
Unnamed Parameters
In ES5, unnamed parameters could be accessed though arguments
object, for example:
function pick(object) {
let result = Object.create(null);
for(let i=1; i<arguments.length; i++) {
result[arguments[i]] = object[arguments[i]];
}
return result;
}
let post = {
'title': 'ES6 Fucntion',
'author': 'Wen Zhu',
'year': 2017
};
let postData = pick(post, 'title', 'author');
console.log(postData.title); // ES6 Function
console.log(postData.author); // Wen Zhu
In this pick
function, we input an objet and a list of properties we want to get, the response is an object containing those properties and their values. This function works fine, however there are some disadvantages, the first one is it is unclear that this function could take any number of parameters from its function signature and the other one is that the for loop starts from index 1 instead of 0 makes it confusing and buggy-prone.
Rest Parameter
ES6 solves this problem elegantly using rest parameters
, let's rewrite the pick
function in ES6:
function pick(object, ...keys) {
let result = Object.create(null);
for(let i=0; i<keys.length; i++) {
result[keys[i]] = object[keys[i]];
}
return result;
}
let post = {
'title': 'ES6 Fucntion',
'author': 'Wen Zhu',
'year': 2017
};
let postData = pick(post, 'title', 'author');
console.log(postData.title); // ES6 Function
console.log(postData.author); // Wen Zhu
Rest Parameter Restrictions
- It has to be last
- cannot be used in an object literal setter
See two examples here:
function pick(object, ...keys, last) {} // SyntaxError
let object = {
set name(...value) {} // SyntaxError: can't use rest params in setter
}
The second restriction exists because object literal setter only allows one single parameter.
The Spreator Operator
The spreator operator works closely with rest parameters, however rest parameters allow you to specify multiple arguments that should be combined into an array, the spreator operator allows you to split an array into separament arguments passing into the functions. Let's take builtin function Math.max
as an exmaple.
In ES5, we usually utilize the function apply
to pass an array of values into the function:
let values = [12, 42, 1, 43];
console.log(Math.max.apply(Math, values)); // 43
In ES6, the spreator operator makes it easier and more elegantly to pass an array into a function:
let values = [12, 42, 1, 43];
console.log(Math.max.(...values)); // 43
We can also mix the sperator operator with other parameters, for example:
let values = [12, 42, 1, 43];
console.log(Math.max.(...values, 100)); // 100
Arrow Functions
We've discussed the ES6 block-level function in last post. Arrow Functions is a great weapon introduced in ES6 to deal with block-level functions. As the name suggests, Arrow Functions are defined in a new syntax by using an arrow(=>). The arrow functions work differently from the traditional Javasctipt functions in a number of important ways.
- No
this
,super
,arguments
andnew.target
bindings those binds are defined by the cloeset containing non-arrow function. - Cannot be called with
new
Arrow fuction does not have an internal method[[Construct]]
, therefore, it cannot be used as an constructor. - No prototype Because you cannot use
new
with arrow functions, so there's no need for a protype. - Cannot change
this
You cannot usebind
to changethis
value in arrow functions. - No
arguments
object As mentioned in the first point, we can see we cannot reply onarguments
to get the passed parameters. - No duplicate named parameters Arrow functions cannot have duplicate named parameters in both strict and non-strict mode, as opposed to non arrow functions can have duplicate named parameters in non-strict mode.
Those differences make arrow function more concise and less buggy, because this
binding is a major source of bugs in Javascript, it is very easy to lose track of this
value inside a function which can result in unintended behaviors. Another advatange of arrow functions is that the Javascript engine could utilize it to optimize the operations, because unlike regular Javascript functions, arrow functions will not be used as a constructor or otherwise modified.
Arrow Function Syntax
Let's first look at an example:
let add = (value1, values) => {
let sum = value1 + value2;
return sum;
};
From this example, basic syntax of arrow function is it begins with functions parameters, followed by arrow, followed by the function body. The parameters are wrapped by parenthese and the function body is wrapped by curly brace. However, if the function only has one parameter we can eliminate the parenthese and similarly we can remove the curly brace and keyword return
if there's only one return line inside the function body, for example:
let addOne = value => value+1;
But if there's no parameters passed in, we need to keep the parenthese and we also need to keep the curly brace if nothing inside the function body.
let getValue = () => {};
One special case about the return value is when we want to return an object literal, we need the returned object wrapped in a parenthese, for example:
let getItem = id => ({id: id, name: 'Temp'});
Creating IIFE
We can use arrow functions to easily create immediately invoked function expression (IIFE) by wrapping arrow function in parentheses. for example:
let person = ((name) => {
return {
getName: function() {
return name;
}
};
})('Wen');
console.log(person.getName()); // Wen
Note one difference here is that parentheses can only wrap the arrow function definition, not around ('Wen'), as opposed to non arrow function can include the parameters as well.
Tail Call Optimization
This is an internal change to the functions in ES6. A tail call
is when a function is called as the last statement in another function, like this:
function doSomething() {
return doSomethingElse(); // tail call
}
In ES5, tail call is handled just like normal function calls: A new stack frame will be created and pushed onto the call stack to represent the function call. That means all previous function calls are kept in memory, which is problematic when the call stack gets too large. ES6 makes some optimization to tail call in strict mode (no change in non-stricy mode): Instead of creating a new stack frame for tail call, current stack frame will be cleared and reused as long as the following conditions are met:
- The tail call does not require access to variables in current stack (no closure)
- The function making the tail call has no further work to do after the tail call returns.
- The result of the tail call is returned as the function value.
For a better understanding how the optimization works, this is an excellent blog.