ES6 magical stuffs — Spread (…) syntax in depth
A classic problem
Did you ever get into a situation where you need to create a method that receives an indefinite number of arguments? It’s not difficult, you would say, since Javascript provides us the almighty ‘arguments’ as local Array-like (pay attention — Array-like is not an array) object to use within each function. So let’s say if we want to create a method to calculate sum of numbers, not limited to how many numbers passed to it, we can implement as:
function sumOf(){
var sum = 0;
for (var i = 0; i < arguments.length; i++){
sum += arguments[i];
}
return sum;
}
Easy piece right? And of course, it works just fine.
sumOf(1,2,3,4,5);//15
sumOf(1,2);//3
Cool. But… what if we need a method that receives one parameter and unlimited number of arguments after that, because we can only decide based on the first argument what to do with all the other passed arguments (only with them, not including the first one)? For example:
function execute(action, <param1, param2, param3,...>){
if (action === <something>){
doSomething(<param1, param2, param3, ...>)
}
}
How do you achieve this? You may
Try apply()
Nice thing about apply() is that apply() takes single array of parameters (or array-like object) as its arguments, which means we can do:
The downside is it takes too much work:
- Need to remove the first argument from the arguments list.
- Since arguments is only array-like, there is no built-in shift() or splice(), we have to convert it to array first and only then remove the first argument.
The rest is simple — call the action using apply()
and pass null as this
value, and the modified array.
Question: is there any shorter, nicer way to achieve same result?
Thanks to ES6, we finally have the magic of (…) spread syntax.
What is exactly the magic spread syntax?
Simply, spread syntax — written as ‘…’ — allows:
- Iterable object
- Object expression
to be expanded in places where it is expected to have:
- Zero
- Indefinite number of arguments (function calls)
- Indefinite number of elements (array literals)
- Indefinite number of key-value pairs (object literals)
How does it solve our problem?
First and foremost, we can use spread syntax as Rest parameter.
Rest parameter
Rest parameter allows function to receive indefinite number of arguments as an array.
function <function name> (…args)
Note: spread syntax (…) needs to be used as a prefix of the last named parameter.
Unlike arguments
, Rest parameter:
- is actually instances of Array, with full support Array built-in functionalities.
- only contains the ones that haven’t been given a separate name as array elements, while
arguments
contains all parameters passed.
function printMe(a,b,c){
console.log(arguments);
//will print all passed parameters respectively as a, b, c
}function printMeSpread(a, ...args){
console.log(args);
//Will only print from 2nd passed parameters onwards
}printMe(1,2,3);// [1,2,3]
printMeSpread(1,2,3); //[2,3]
Hence, instead of converting arguments
to array, removing the first element (which we don’t need), we can just re-write our function declaration as:
function executeWithSpread(type, ...params){....}
And thanks to it, we saved about 2–3 lines of code. What can be done next?
Getting rid of apply()
Indeed, we can use it as replacement for apply()
, then our function will become:
Here when we use ‘…’ syntax as prefix of params
, all elements of params
will be spread (expanded) as arguments, instead of being one argument params
.
Further explanation:
- On
executeWithSpread()
, params will converted to an array containing [1,2,3,4,5] by using...params
in method declaration. - On
Sum()
, by passing params as...params
,arguments
will be[1,2,3,4,5]
instead of[[1,2,3,4,5]]
And hence no need for null
or any further unnecessary noise.
But if you still keen on combining with apply()
, we can still do as following:
Either way, it’s still cleaner, lesser chance for bug, don’t you agree?
But not only that, as said, spread syntax does magic. And by “magic”, I mean it is a powerful tool, despite its simplicity in appearance. So…
What else does it offer to us?
Clone an object
One of the common way in copying an object is to use Object.assign()
to create shallow clone version.
Spread syntax gives us a shorter and cleaner way
Similar to Object.assign()
using spread (…) syntax will only initialize copying object’s own enumerable properties to new object.
Clone an array
Instead of using shallow copying an array using Array.slice(0)
Or using Array.from()
We can use Spread syntax to achieve the same effect —but remember, it will only go one depth level, same as Array.slice()
and Array.from()
Convert an iterable object to array
Probably a lot of us already knew and used this functionality 😃 — remember the infamous Remove duplicates from given array problem? 😁
Yup, instead of using a loop to iterate and copy each element to new array, we can shorten the code by using Spread (…) syntax as above. It will spread all elements of Set into new array respectively without a lot of efforts required (of course, less chance for 🐛).
Same can go for converting a Map object to array, in which each of pair <key, value> will be represented as sub-array element in the new array.
❌ But definitely not being able to convert Object, since it’s not iterable.
Merge arrays
Normally, if we want to merge two arrays or more into one, or simply just want to concatenate one array to another, we will use
Array.prototype.concat()
which will return new array in which, all elements of additional array will be added to the end of original array. This won’t modify original array.
Array.prototype.unshift()
which will add all elements of additional array to the beginning of original array. This will indeed modify original array.
However, with Spread syntax, we can define the merging order easily as:
Note that in this case, both original arrays are not modified.
Merge objects’ properties
Literally, which one do you prefer? This way (which modifies original object)
for(var key in obj2){
obj1[key] = obj2[key];
}
Or simply this way
Object.assign(obj1, obj2)
Or even simpler? (not modifies original object)
var mereObj = {...obj1, ...obj2}
Using with constructor
When using constructor withnew
syntax, it is impossible to directly use apply()
and array together — in ES5, in order to create new Array from existing array, we will have to do as following:
var arr = new (Function.prototype.bind.apply(Array, [null].concat([1,2,3,4])));
Is it not confusing enough? At least for me it’s. Luckily, Spread syntax is there for us:
var arr = new Array(...[1,2,3,4]);
Shouldn’t we appreciate this magic 😃?
Other mix-in usages
Last but not least, with the help of Spread syntax, we can finally have much more powerful array literal, where we don’t have to ask for the help of additional functionalities like push
, splice
, concat
, etc… when needed.
var arr1 = ['hello', 'there'];
var arr2 = ['today', 'is', 'good', 'day', ...arr1, 'how', 'are', 'you']console.log(arr2); //['today', 'is', 'good', 'day', 'hello', 'there', 'how', 'are', 'you']
Awesome right?
And of course, there is no limit to number of times we can use this syntax in array literal.
Conclusion
Personally I found this syntax is rather useful than following the traditional way — which requires long line of code and if you are not careful, you may end up with a 🐛without noticing, and that, for me, is really annoying.
However, in exchange for cleanness and convenience, sometimes you may find it harder to read/understand. (after all, for the first time-r(s), who will be able to understand what a bunch of ‘…’ stands for?)
Regardless, as one of #1 rules in programming stated — rule of KISS (Keep It Simple Stupid), we should always try to keep our code simple as much as we can, and less code but good code is never bad (especially in JS when number of code lines need to be loaded DOES matter to web performance).
So why not? If it is a good magic, we’d better take advantage of it, as a better upgrade for our own magic creation. 😄
Do you agree with me? Let me know in comments. I’d love to hear your opinions.
More on ES6:
- Set vs Array — What and when?
- Map vs Object — What and when?
- ES6 Cool stuffs — var, let and const in depth
- ES Cool stuffs — Destructuring statement in depth
- ES6 Cool stuffs — Template literals in depth
- Let’s divide our phones into Classes
If you like this post, don’t forget to give me a 👏 below ⏬️ . It will surely motivate me a lot 😊
If you love to read more, feel free to check out my articles.
If you’d like to catch up with me sometimes, follow me on Twitter | Facebook or simply visit my portfolio website.