Javascript 101
2.6 Rest and Spread Operators
2.6 Rest and Spread Operators
ES6 comes with a powerful and useful operator call the rest/spread operator
denoted by ...
.
Rest Operator
It allows us to match and group up function arguments into an array. The idea is
quite intuitive after seeing an example. Suppose we have the following problem
where we want to define a sum
function which can take in numeric values and
return the sum of all of them.
function sum(a, b, c) { return a + b + c;}
console.log(sum(1, 2, 3)); //6
//unfortunately, this function only works for the case where//3 values are suppliedconsole.log(sum(1, 2, 3, 4)); //6
//does not even work for 2 argument caseconsole.log(sum(10, 20)); //NaN
It turns out that JS provides a special object (arguments
) that is accessible
in the function by which we can get the list of all the arguments in a function
invocation. So in order to solve the above problem, we could instead do this:
function sum1() { let sum = 0; for (let i = 0; i < arguments.length; i++) { sum += arguments[i]; }
return sum;}
console.log(sum1(1, 2, 3, 4)); //10console.log(sum1(10, 20)); //30
While the above program is totally valid JS codes, the code is not so readable/intuitive to a programmer (who is reading someone else's codes). ES6 comes with the rest operator that can make the codes more readable.
function sum2(...values) { let sum = 0; for (let i = 0; i < values.length; i++) { sum += values[i]; }
return sum;}
console.log(sum2(1, 2, 3, 4)); //10console.log(sum2(10, 20)); //30
Now, it is more obvious that the function sum2
is supposed to take in one or
more values. The syntax for the rest operator consists of ...
and an
variable name. The arguments are now accessible by the variable name as
an array. Another difference between using the rest operator as oppose to
arguments
is that the rest operator can match by the position rather than
giving all the arguments like in the case of arguments
.
//method to return a greetingsfunction generateGreeting(name, gender, ...items) { let title; if (gender === "M") { title = "Hi! Mr"; } else { title = "Hi! Ms"; }
if (items.length > 0) { return `${title} ${name} has ${items}`; } else { return `${title} ${name}`; }}
console.log(generateGreeting("John", "M", "iPad", "iPhone"));//Hi! Mr John has iPad,iPhone
console.log(generateGreeting("Jane", "F"));//Hi! Ms Jane
Notice that items
can be used to group all the arguments after the second
arguments (probably the reason why it was given the name rest - i.e. rest of
the arguments). The function is also more readable. Of course, we could have
just supplied an array of items instead of calling the function with multiple
arguments.
The rest operator is used to group up the rest of the arguments
As mention above, one way to look at the rest operator is to think of it as a way to group up the rest of the arguments that we have in the function. What this means is that there should not be any arguments after the rest operator. JS will complain that there is a syntax error if we do that.
//SyntaxError: Rest parameter must be last formal parameterfunction foo(a, b, ...c, d){ //...}
Spread Operator
There is also a spread operator which essentially does the opposite. It is sometimes used together with the rest operator but the spread operator can be used in more scenarios:
Array Literal
The spread operator can allow us to access and manipulate all the values from arrays rather that explicitly access using indexing.
//normally can't concat 2 arrays with just arraysvar array1 = [1, 2, 3];var array2 = ["a", "b", "c"];
var combine = [array1, array2];console.log(combine);//[[1, 2, 3], ['a', 'b', 'c']]
//if want to do that, need to use concat()var flattenCombine = array1.concat(array2);console.log(flattenCombine);//[1, 2, 3, "a", "b", "c"]
var array1 = [1, 2, 3];var array2 = ["a", "b", "c"];
//now possible to combine 2 arrays using the//spread operator (concat/prepend cases)var flattenCombineSpread = [...array1, ...array2];console.log(flattenCombineSpread);//[1, 2, 3, 'a', 'b', 'c']
var array1 = [1, 2, 3];
//combine the elements of an array with other itemsvar combine2 = [0, ...array1, "d"];console.log(combine);//[0, 1, 2, 3, 'd']
var array1 = [1, 2, 3];var array2 = ["a", "b", "c"];
//possible to have multiple spread operatorsvar flattenCombineSpread2 = [0, ...array1, ...array2, "d"];console.log(flattenCombineSpread2);//[0, 1, 2, 3, "a", "b", "c", "d"]
//copying/cloning arrayvar array1 = [1, 2, 3];
var array1CopySlice = array1.slice();console.log(array1CopySlice);//[1, 2, 3]
//now with spread operatorvar array1Copy = [...array1];console.log(array1Copy);//[1, 2, 3]
Convert "array-like" objects to array
//get all <p> tagconst allPara = document.querySelectorAll("p");
//seems to be array but take a look at the __proto__ property//__proto__: NodeList//not array!console.log(allPara);
//2 ways to convert to array
//1) Using Array.from()//now take a look at the __proto__ property//__proto__: Array(0)const allParaArr1 = Array.from(allPara);console.log(allParaArr1);
//2) Using spread + array literal//now take a look at the __proto__ property//__proto__: Array(0)const allParaArr2 = [...allPara];console.log(allParaArr2);
Function Call
Rather than explicitly supply the function parameters one by one, the spread operator allows us to spread out the values from an array as function parameters in function invocations.
var nums = [1, 2, 3, 4];
function sum2(...values) { let sum = 0; for (let i = 0; i < values.length; i++) { sum += values[i]; }
return sum;}
console.log(sum2(...nums));//same as://console.log(sum2(1, 2, 3, 4));//10
//possible to get inputs from multiple arraysvar nums1 = [1, 2];var nums2 = [3, 4];
function sum2(...values) { let sum = 0; for (let i = 0; i < values.length; i++) { sum += values[i]; }
return sum;}
console.log(sum2(...nums1, ...nums2));//same as://console.log(sum2(1, 2, 3, 4));//10
//can use together with explicit argumentsvar nums1 = [1, 2];var nums2 = [4, 5];
function sum2(...values) { let sum = 0; for (let i = 0; i < values.length; i++) { sum += values[i]; }
return sum;}
console.log(sum2(...nums1, 3, ...nums2));//same as://console.log(sum2(1, 2, 3, 4, 5));//15
Object Literal
The spread operator also works for object literals. We can spread out the
key-values from an object into another object. Same idea as with the array
literals, we can access the object property-values directly rather than having
to do indexing or calling .property
. By using the spread operator, we can add
on additional properties.
var personName = { firstName: "John", lastName: "Doe" };var personContact = { mobile: "91234567" };
var person = { ...personName, ...personContact };console.log(person);//{firstName: "John", lastName: "Doe", mobile: "91234567"}
It can also be to override/replace existing properties.
var person = { firstName: "John", lastName: "Doe", mobile: "91234567" };var newContact = { mobile: "123456789" };
person = { ...person, ...newContact };console.log(person);//{firstName: "John", lastName: "Doe", mobile: "123456789"}
The order by which the spread operator is used in object literals matters as the ones on the right side will override the ones on the left side (and also add new properties).
var person = { firstName: "John", lastName: "Doe", mobile: "91234567" };var contactDetails1 = { mobile: "123456789" };var contactDetails2 = { email: "123456789" };var contactDetails3 = { mobile: "012345678" };
person = { ...person, ...contactDetails1, ...contactDetails2, ...contactDetails3,};console.log(person);//{firstName: "John", lastName: "Doe", mobile: "012345678", email: "123456789"}
Others
There are also other situations where the spread operators can be used. For example, getting the individual characters of a string as an array:
var s = "hello";var chars = [...s];console.log(chars);//["h", "e", "l", "l", "o"]