Fun with JavaScript's spread (...) operator

Fun with JavaScript's spread (...) operator

15 Aug 2021

Spread syntax landed in JavaScript in ECMA's 9th edition, in 2018. It's a quirky yet super-useful means of unpacking arrays, strings or objects in situations where we need to include all their constituent parts.

In all cases, it's denoted by ....

A better form of apply() 🔗

One simple example is as a replacement for apply(), which traditionally has been used as a means to pass array values as individual function arguments:

let nums = [13, 5, 91]; //before Math.sum.apply(null, nums); //now Math.sum(...nums);

Because sum() expects numbers to be passed separately as individual arguments, the spread operator (like apply()) is great for unpacking our array values in this way.

In fact, this circumvents a weakness in apply(), which is that it cannot be used with constructors, i.e. with the new keyword.

new Date.apply(null, [1980, 5, 29]); //error - Date.apply() is not a constructor

You know what you meant, but the JS engine didn't. With spread syntax, no such problem.

new Date(...[1980, 5, 29]);

Unpacking strings 🔗

Likewise with strings; they are unpacked into individual characters:

function firstLetter(letter) { console.log(letter); } firstLetter(...'abc'); //"a"

A spot of code golf 🔗

But it gets cooler than that. Suppose you had an array of words and wanted to categorise them into an object by their starting letter.

let words = [ 'apple', 'pear', 'angry', 'pipe', 'xylophone' ];

We might do this, which is perfectly fine:

let byFirstLetter = null; words.forEach(word => { if (!byFirstLetter[word[0]]) byFirstLetter[word[0]] = []; byFirstLetter[word[0]].push(word); });

Simple: we iterate over our words, and for each word check whether we already have an array in our object for words beginning with that the current word's first letter. If not, we create it. Then we push the word into the array. This gives our desired result:

{ a: ['apple', 'angry'], p: ['pear', 'pipe'], x: ['xylophone'] }

But if code golf is your thing, we can achieve the same thing with less code via the spread operator.

words.forEach(word => { byFirstLetter[word[0]] = [...(byFirstLetter[word[0]] || []), word]; });

What we're doing there is basically the same thing, but in one line. We're still checking whether the array already exists, but in a different way. We're unpacking either the existing array, or, if it doesn't yet exist, a new one. We're then merging - i.e. pushing - into that array (whichever prevailed) a new value, word.

Simpler merging/concatenation 🔗

Implicit in the above example is another feature of the spread operator - namely, merging/concatenating one array/object into another. A simpler example of the above is like so:

let obj1 = {a: 'b'}, obj2 = {...obj1, c: 'd'}; console.log(obj2); //{"a": "b", "c": "d"}

Likewise with arrays:

let arr1 = [1, 2, 3], arr2 = [4, 5, 6], arr3 = [...arr1, 7, 8, 9, ...arr2]; console.log(arr3); //[1, 2, 3, 7, 8, 9, 4, 5, 6]

Summary 🔗

The spread operator is super convenient when it comes to unpacking strings, arrays or objects in situations where each constituent part needs to be presented as part of a list - for example, when you want to pass each part as a separate argument to a function or constructor.

It's also super useful for concatnating or merging arrays and objects into new arrays or objects.

Did I help you? Feel free to be amazing and buy me a coffee on Ko-fi!