Techniques With ES6 Default Parameter Values, Spread, and Rest
Can Ho explores required parameters, spreads, arrays, and more in ES6 while often showing us the equivalent actions in ES5.
Join the DZone community and get the full member experience.
Join For FreeDefault Behavior
Default parameter values let function parameters be initialized with default values when no value or undefined is passed.
function join(arr=[], sep=','){
return arr.join(sep);
}
join();//""
join([1,2,3]); //"1,2,3"
join(["Javascript", "is", "awesome"], " "); //"Javascript is awesome"
We can also specify a function as a default value.
import rp from 'request-promise';
function jsonParser(body, response) {
if (/^application\/json.*/i.test(response.headers['content-type'])){
return JSON.parse(body);
}
return body;
}
function fetch(url, transform=jsonParser) {
return rp({
url: url,
transform: jsonParser
});
}
Required Parameters
Another technique with default parameter values is to allow a function to declare required parameters. This is really useful when designing APIs that need parameter validation.
function required(param){
throw new Error(`${param} is required`);
}
const Storage = {
setItem: function setItem(key = required('key'), value=required('value')){
//implentation code goes here
},
getItem: function getItem(key = required('key')){
}
}
Storage.setItem();//Uncaught Error: key is required
Storage.setItem('key1');//Uncaught Error: value is required
Storage.setItem('key1', 'value1'); //OK
Copy Arrays and Modify Them
In ES5, we can use Array#concat or Array#slice to make a copy of an array.
var arr = [1, 2, 3, 4, 5];
var arr2 = arr.slice(0); //1, 2, 3, 4, 5
var arr3 = [].concat(arr); //1, 2, 3, 4, 5
In ES6, copying an array is even easier with the spread operator.
const arr = [1, 2, 3, 5, 6];
const arr2 = [...arr]; //1, 2, 3, 5, 6
const b = [...arr.slice(0, 3), 4, ...arr.slice(3)];//1, 2, 3, 4, 5, 6
Copy Objects and Modify Them
In ES5, we can borrow a utility function such as jQuery#extend, _.assign to make a copy of an object:
var o = {
name: 'John',
age: 30,
title: 'Software Engineer',
}
var o2 = _.assign({}, o);
var o3 = _.assign({}, o, {age: 25});
In ES6, we can use the built-in function Object.assign:
const o = {
name: 'John',
age: 30,
title: 'Software Engineer',
}
const o2 = Object.assign({}, o);
const o3 = Object.assign({}, o, {age: 25});
Another way is to use the spread (…) operator:
const o2 = {
...o
}
const o3 = {
...o,
age: 25
}
Note: spread operator for objects isn't supported in ES6. Hopefully, this will be included in ES7. If you're using a transpiler like Babel, you're covered.
Avoid Function.prototype.apply
Some functions such as Math.max, Math.min, Date, etc. require a list of parameters.
Math.max(1, 100, 90, 20);
new Date(2016, 7, 13);
What if we have a list of parameter values contained in an array? A workaround is to use Function.prototype.apply(thisArg, [])
var numbers = [1, 100, 90, 20];
Math.max.apply(null, numbers); // 100
In ES6, this can be solved easily with the spread operator:
var numbers = [1, 100, 90, 20];
Math.max(...numbers);
var parts = [2016, 7, 13];
var d = new Date(...parts);
Forget Arguments
arguments is an array-like object that is available inside function calls. It represents the list of arguments that were passed in when invoking the function. There're some gotchas:
- arguments is not an array. We need to convert it to an array if we want to use array methods such as slice, concat, etc.
- arguments may be shadowed by function parameters or a variable with the same name.
function doSomething(arguments) {
console.log(arguments);
}
doSomething(); //undefined
doSomething(1); //1
function doSomething2() {
var arguments = 1;
console.log(arguments);
}
doSomething2();// 1
doSomething2(2, 3, 4); // 1
In ES6, we can completely forget arguments. With rest(…) operator, we can collect all arguments passed function calls:
function doSomething(...args) {
console.log(args);
}
doSomething(1, 2, 3, 4); //[1, 2, 3, 4]
With rest operator, all arguments passed to doSomething are collected into args. More than that, args is an array, so we don't need an extra step for converting to an array as we did for arguments.
All in One
In this part, we will use techniques above in a complex case. Let's implement the fetch API. For simplicity, we build the API on top of request-promise module.
function fetch(url, options){
}
The first step is parameter checking:
//ES5
import rp from 'request-promise';
function fetch(url, options){
var requestURL = url || '';
var opts = options || {};
...
}
//ES6
import rp from 'request-promise';
function fetch(url='', options={}){
...
}
We also need to check some properties of the options object:
function jsonParser(body, response) {
if (/^application\/json.*/i.test(response.headers['content-type'])){
return JSON.parse(body);
}
return body;
}
//ES5
import rp from 'request-promise';
function fetch(url, options){
var requestURL = url || '';
var opts = options || {};
var method = options.method || 'get';
var headers = opts.headers || {'content-type': 'application/json'};
var transform = jsonParser;
...
}
//ES6
import rp from 'request-promise';
function fetch(url='', {method='get',
headers={'content-type': 'application/json'},
transform=jsonParser}){
}
In the ES6 version of the API, we use destructuring to extract some properties (method, headers, and transform) and set some default values. This doesn't work if we don't pass the options object because we can't match a pattern against undefined:
fetch();//TypeError: Cannot match against 'undefined' or 'null'
This can be fixed by a default value:
//ES6
import rp from 'request-promise';
function fetch(url='', {method='get',
headers={'content-type': 'application/json'},
transform=jsonParser} = {}){
return rp({
url: url,
method: method,
headers: headers,
transform: transform
});
}
As client code may pass properties other than method, headers, and transform, we need to copy all remaining properties:
//ES5
import rp from 'request-promise';
function fetch(url, options){
var requestURL = url || '';
var opts = options || {};
var method = options.method || 'get';
var headers = opts.headers || {'content-type': 'application/json'};
var transform = jsonParser;
//copy all properties and then overwrite some
opts = _.assign({}, opts, {method: method, headers: headers, transform: transform})
return rp(opts);
}
In ES6, we need to collect remaining properties by using the rest operator:
function fetch(url='', {method='get',
headers={'content-type': 'application/json'},
transform=jsonParser,
...otherOptions} = {}){
}
...And using the spread operator to pass those properties to the target function:
function fetch(url='', {method='get',
headers={'content-type': 'application/json'},
transform=jsonParser,
...otherOptions} = {}){
return rp({
url: url,
method: method,
headers: headers,
transform: transform,
...otherOptions
});
}
And finally, with object literal shorthand, we can write this:
function fetch(url='', {method='get',
headers={'content-type': 'application/json'},
transform=jsonParser,
...otherOptions} = {}){
return rp({
url,
method,
headers,
transform,
...otherOptions
});
}
Published at DZone with permission of Can Ho, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments