Home | Send Feedback

JavaScript spread and rest syntax examples

Published: 19. January 2019  •  javascript

In this blog post, you find a collection of examples revolving around the JavaScript spread and rest syntax (...). These are not new examples, and you have probably already seen a few of them on the Web. I just wanted to collect them here to have a reference.

Introduction

Spread syntax

With the spread syntax, JavaScript code can expand any iterable, such as arrays or strings, in place. A use case is passing arrays to methods that expect zero or more arguments instead of arrays.

function sum(a, b, c) {
    return a + b + c;
}

const input = [1,2,3];
sum(input); // wrong

When you call the function with sum(input), you get the wrong result. JavaScript assigns the argument input to a, b and c are undefined. To resolve this, you have to spread the array so that each element is a single argument.

sum(...input);
// equivavlent to
sum(input[0], input[1], input[2]);

Prominent examples that use this pattern are Math.min and Math.max.

Math.min(...input) === 1;
Math.max(...input) === 3;

If you call such a method with fewer arguments than the method expects, the parameters without a value will be set to undefined.

sum(...[1,2]); // c === undefined

Excessive arguments are ignored

sum(...[1,2,3,4]); // forth array element ignored

Another use case of the spread syntax is to use it in array literals, to copy or merge arrays.

const a = [1, 2, 3];
const copyOfA = [...a]; // [1, 2, 3]

// merge two arrays
const a = [1, 2, 3];
const b = [4, 5, 6];
const c = [...a, ...b]; // [1, 2, 3, 4, 5, 6]

Be aware when working with arrays of objects, because the spread syntax only creates a shallow copy.

const john = {name: 'John'};
const jane = {name: 'Jane'};

const persons = [john, jane];
const copyOfPersons = [...persons]; // copies just the references, not a deep copy

john.name = 'John Doe';

//change reflected in both arrays
persons[0].name === 'John Doe';
copyOfPersons[0].name === 'John Doe';

Since ES2018, the spread syntax also supports objects. You can use this for copying objects or for applying default property values.

const user = {id: 1, name: 'John'};
const copyOfUser = {...user}; // {id: 1, name: 'John'}

The spread syntax only copies own enumerable properties.
Be aware that this is just a shallow copy. Nested objects point to the same object.

const hobbies = ['reading'];
const person = {id: 2, name: 'Jane', hobbies};
const copyOfPerson = {...person};

hobbies.push('jogging');

// person.hobies -> ["reading", "jogging"]
// copyOfPerson.hobbies -> ["reading", "jogging"]

const defaultSettings = {timeout: 1000, packetSize: 10};
const userSettings = {packetSize: 20, iterations: 5};

const settings = {...defaultSettings, ...userSettings}; // {timeout: 1000, packetSize: 20, iterations: 5}

Note that always the last property "wins", if the property is part of multiple objects. In this example the value of packetSize in userSettings overrides the value from defaultSettings.


Source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax


Rest syntax

Looks the same as the spread syntax (...), but does the opposite. It collects multiple elements and puts them into a single element.

If a method parameter is prefixed with ..., all remaining arguments will be placed into an array. Notice that only the last parameter can be a rest parameter.

function print(a, b, ...others) {
   console.log(a, b, others);
}

print(1, 2, 3, 4, 5, 6);
// a === 1
// b === 2
// others -> [3, 4, 5, 6]

If there are no remaining arguments, the rest parameter is an empty array [].

print(1,2);
// a === 1
// b === 2
// others -> []

print(1)
// a === 1
// b === undefined
// others -> []

Source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/rest_parameters

Examples

Converting Maps and Sets to JSON

When you are working with ES2015 Map and Set, you've maybe noticed that they can't be converted to JSON.

const aMap = new Map().set(1, 'one').set(2, 'two');
const aSet = new Set().add('reading').add('swimming');

const mapJson = JSON.stringify(aMap); // === "{}" 
const setJson = JSON.stringify(aSet); // === "{}"

The stringify calls don't fail but return "{}" which is not that useful. The solution is to use the spread syntax and expand the elements from the collection into an array.

const mapJson = JSON.stringify([...aMap]); // === "[[1,"one"],[2,"two"]]" 
const setJson = JSON.stringify([...aSet]); // === "["reading","swimming"]"

From this form, it is also easy to deserialize them back into a Map resp. Set

const anotherMap = new Map(JSON.parse(mapJson));
const anotherSet = new Set(JSON.parse(setJson));

If you have an object with nested Maps and Sets, you can use a replacer function to serialize them into JSON.

const obj = {
    hobbies: new Set(['jogging', 'biking']),
    metadata: new Map([['header', 'somedata'], ['auth', 'token']])
};

function replacer(key, value) {
  if (value instanceof Map || value instanceof Set) {
    return [...value];
  }
  return value;
}

const json = JSON.stringify(obj, replacer);
// "{"hobbies":["jogging","biking"],"metadata":[["header","somedata"],["auth","token"]]}"

Source: https://2ality.com/2015/08/es6-map-json.html


Remove elements or properties

With the rest syntax and destructuring, you can "remove" elements from an array. This pattern does not remove elements; it creates a copy of an existing array or object and ignores certain elements or properties.

This example copies an array without the first element.

const data = [1, 2, 3];

const [first, ...rest] = data;
//first === 1
//rest -> [2, 3]

Array.shift() does the same, it removes the first element of an array but it changes the array, whereas with this pattern the source array is not changed.

This does not work if you want to create a copy without the last element, because the rest element must be the last

const [...rest, last] = data; // error

This pattern also works with properties in objects (since ES2018)

const user = {
    id: 1,
    username: 'john',
    email: 'jd@email.com',
    password: 'very secret'
};

const {
    password,
    email,
    ...publicUser
} = user;

//password === "very secret"
//email === "jd@email.com"
//publicUser -> {id: 1, username: "john"}

The source object user stays unchanged.

Notice that this approach creates variables (password, email, first) in the current scope. If they are not needed, you could rename them to make it more evident that they should not be used.

// create variables _ and rest
const [_, ...rest] = data;

// create variables _p, _e, and publicUser
const {
    password: _p,
    email: _e,
    ...publicUser
} = user;

Source: https://www.bram.us/2018/01/10/javascript-removing-a-property-from-an-object-immutably-by-destructuring-it/


Conditionally adding elements and properties

If you combine the spread syntax with the ternary operator, you have a concise way to add elements to an array based on a condition.

const important = true;
const data = [
  ...(important ? [1, 2, 3] : []),
  4, 5, 6
];

// important === true   -->  data -> [1,2,3,4,5,6]
// important === false  -->  data -> [4,5,6]

This works because ...[] does not return anything.


Since ES2018, you can utilize the same pattern for conditionally adding properties to objects.

const temperature = true;
const sensorData = {
    id: 1,
    sensorName: 'ES9283',
    ...(temperature ? {temp: 23.5} : {}),
    humidity: 67
};

// temperature === true  --> sensorData -> {id: 1, sensorName: 'ES9283', temp: 23.5, humidity: 67}
// temperature === false  --> sensorData -> {id: 1, sensorName: 'ES9283', humidity: 67}

Source: https://2ality.com/2017/04/conditional-literal-entries.html


Remove duplicates from an array

An easy way to remove duplicates from an array is to convert it into a Set and back into an array. The spread syntax expands all elements from the Set into the array literal.

const someData = [1, 1, 1, 2, 3, 4, 4, 4, 4];
const distinctData = [...new Set(someData)]; // [1,2,3,4]

Source: https://www.bram.us/2017/02/07/es2015-es6-easily-remove-duplicates-from-an-array/


Make your classes spreadable

The spread syntax utilizes an iterator to expand the elements.

...[1,2,3]
...{a: 'one', b: 'two'}
..."strings"
...Map()
...Set()

If you create your class and then try to use the spread syntax, an error occurs because your object does not implement iterable by default.

class Hobbies {
  constructor() {
    this.hobby1 = 'reading';
    this.hobby2 = 'swimming';
  }
}

const hobbies = new Hobbies();
const expanded = [...hobbies]; // TypeError: object is not iterable 

To make this class iterable, we have to implement the iterator method that has to return an object with a next() function. This can be simplified by implementing a generator function that returns a Generator object and this object conforms to the iterable protocol because it implements the next method.

class Hobbies {
  constructor() {
    this.hobby1 = 'reading';
    this.hobby2 = 'swimming';
  }
  
  *[Symbol.iterator]() {    
      yield this.hobby1;     
      yield this.hobby2;
  }
}

const hobbies = new Hobbies();
const expanded = [...hobbies]; // ['reading', 'swimming']