Javascript’s Awesome Spread Syntax[…]

Steven Ellis
ITNEXT
Published in
6 min readJun 28, 2019

--

Linus Torvalds, the founder of Linux, once said that good programmers don’t worry about code, but rather about data structures and their relationships.

If that’s the case, it follows that good programming languages provide great tools to manage data structures and their relationships — thereby helping programmers focus on what’s important, and not sweat the details over implementation.

Javascript’s recently added spread syntax is a good case in point.

Spread syntax is a very useful declarative tool, and now works on two data structure types: iterables and object literals.

Spread syntax allows an iterable or to be expanded in places where zero or more arguments (for function calls) or elements (for array literals) are expected.

Spread syntax allows an object literal to be expanded in places where zero or more key-value pairs are expected.

But what’ an iterable?

An iterable object is any object that allows you to iterate over all its values by using a for (var value of iterable) { } loop.

It achieved this by under-the-hood returning a function that produces an Iterator for its Symbol.iterator property.

In Javascript, certain object types like Array, Map, Set and String, have built-in iterables with a default iteration behaviour.

For example, an array:

const arr = [‘a’, ‘b’, ‘c’];for (const x of arr) {   // here's the for..of statement
console.log(x);
}

This produces:

abc

Under the hood, ‘for (const x of arr)’ from the snippet above does the following:

const iterator = arr[Symbol.iterator]();
let current = iterator.next();
while (!current.done) {
console.log(current.value);
current = iterator.next();
}

Strings are essentially just collections of Unicode characters, and most of the array concepts apply to them. Strings therefore also have a built-in iterator. String’s default iterator returns the string’s characters one by one.

const stringExample = 'hi';
typeof stringExample[Symbol.iterator]; // "function"
const iterator = stringExample[Symbol.iterator]();iterator + ''; // “[object String Iterator]”
iterator.next(); // { value: "h", done: false }
iterator.next(); // { value: "i", done: false }
iterator.next(); // { value: undefined, done: true }
for(const element of stringExample) {
console.log(element) // produces 'h' and 'i';
}

And what’s an object literal?

A JavaScript object literal is a comma-separated list of key-value pairs wrapped in curly braces.

const myObject = {
sProp: 'some string value',
numProp: 2,
bProp: false
};

The for…in loop syntax can also be used on object literals, to iterate over its enumerable properties (which are the key-value pairs). This loop includes inherited properties from prototype chain.

let obj = {
key1: "value1",
key2: "value2",
key3: "value3"
};
for (const key in obj) {
console.log(`${key} = ${obj[key]}`); // 'key1 = "value1"' etc..
}

OK, so now we have a handle on iterables and object literals.

To reiterate, the spread syntax expands iterables and object literals where zero or more arguments / elements are expected.

Let’s see examples of this:

function sum(x, y, z) { //expects three arguments, returns their sum
return x + y + z;
}
const numbers = [1, 2, 3];
console.log(sum(...numbers));

In this example, we’ve passed the array into the function sum. This function expects three arguments. Spread syntax has expanded the array’s iterable elements, and passed the result (three separate array elements) into the function.

const someString = "hi";
[...someString] // ["h", "i"]

In the above example, spread syntax expands the string into its iterable components (each character).

Examples of Spread Syntax in Action

Let’s see some useful examples of spread syntax.

1. Cloning an array

const origArr = [1,2,3];// traditional ways of cloning arrays ⬇
const copy = [].concat(origArray);
const copy2 = origArray.slice();
const copy3 = Array.from(origArr);
// using the spread operator ⬇
const copy4 = [...origArr]; // much more succinct!

2. Cloning an object literal

Using spread clones enumerable properties from a provided object literal onto a new object.

const obj1 = { foo: 'bar', x: 42 };
const obj2 = { foo: 'baz', y: 13 };
const clonedObj = { ...obj1 };
// Object { foo: 'bar', x: 42 }
const mergedObj = { ...obj1, ...obj2 };
// Object { foo: 'baz', x: 42, y: 13 }

Note that cloning via spread is always shallow!

This means that if one of the original property values is an object, the clone will refer to the same object; it does not recursively clone objects within objects:

const original = { prop: {} };
const clone = {...original};
console.log(original.prop === clone.prop);
//returns true, as cloned property points to same object as original
original.prop.foo = 'abc';
console.log(clone.prop.foo); // abc
// ⬆ (cloned property points to same object as original!)

3. Concatenating arrays

const arr1 = [1,2,3];
const arr2 = [4,5,6];
// The old way:
const arr3 = arr1.concat(arr2);
// With spread syntax this becomes:
const arr4 = [...arr1, ...arr2];
// Merging two arrays:
const parts = ["shoulders", "knees"];
const lyrics = ["head", ...parts, "and", "toes"];
// lyrics = ["head", "shoulders", "knees", "and", "toes"]

4. Inserting an array at the beginning of another array

Array.prototype.unshift() is often used to insert an array of values at the start of an existing array. Without spread syntax this is done as:

let arr1 = [0, 1, 2];
let arr2 = [3, 4, 5];
Array.prototype.unshift.apply(arr1, arr2)
// arr1 is now [3, 4, 5, 0, 1, 2]

With spread syntax, this becomes:

let arr1 = [0, 1, 2];
let arr2 = [3, 4, 5];
arr1 = [...arr2, ...arr1];
// arr1 is now [3, 4, 5, 0, 1, 2]

5. Remove any duplicates from an array of primitives

const arr = [1, 1, 3, 4, 5, 6, 6, 7, 8, 4, 3, 5, 4, 3, 2, 8, 7, 5];
const unique = [...new Set(arr)]; // [1, 3, 4, 5, 6, 7, 8, 2]

6. Using Math functions, and function calls in general

Spread can be used as the argument or functions that can accept any number of arguments.

let numbers = [9, 4, 7, 1];
Math.min(...numbers); // 1

The Math object’s set of functions is a perfect example of the spread operator as the only argument to a function.

It is common to use Function.prototype.apply() in cases where you want to use the elements of an array as arguments to a function. Spread syntax renders this redundant.

function myFunction(x, y, z) { }
var args = [0, 1, 2];
myFunction.apply(null, args);
// With spread syntax the above can be written as:function myFunction(x, y, z) { }
var args = [0, 1, 2];
myFunction(...args);

7. Conditional object properties

const getUser = (emailIncluded) => {
return {
name: ‘John’,
surname: ‘Doe’,
...emailIncluded && { email : 'john@doe.com' }
}
}
const user = getUser(true);
console.log(user);
// outputs { name: “John”, surname: “Doe”, email: “john@doe.com” }
const userWithoutEmail = getUser(false);
console.log(userWithoutEmail);
// outputs { name: “John”, surname: “Doe” }

8. Generate an array of numbers from 0 to 99

const arr = [...Array(100)].map((_, i) => i);

The spread operator loops through the array, and creates a new index key for each array element, starting at 0 and incrementing by 1 iteratively, with a value of undefined at index.

The result is then map-able. In the example, the array map function returns each array element’s index key value, ultimately returning an array of integers from 0 to 99.

--

--

Computer scientist, currently also pursuing a Masters in Data Science.