DEV Community

Thomas Graf
Thomas Graf

Posted on

JavaScript: dot dot dot by example

Do you know all use cases for the three dots in JavaScript?

Little History:

First, they added this syntax for arrays and parameters in ES6 (spread and rest elements).
In 2018, they added it for objects (spread and rest properties).

And all features share the same syntax

...

That can be pretty confusing if you see that syntax for the first time.
This post is a collection of examples how you can use them in your code.

Let's start with: Spread Elements

Want to copy all elements from an array to another array?
Use ... (spread elements) to shallow(!) copy an array.

const fruits = ["apple", "banana", "coconut"];
const fruitsCopied = [...fruits];
// fruitsCopied: ["apple", "banana", "coconut"]

You can adds another element on the fly.

const fruits = ["apple", "banana", "coconut"];
const moreFruits = [...fruits, "pear"];
// moreFruits: ["apple", "banana", "coconut", "pear"]

Let's imagine that we have this stupid print function.
With ... we can simply spread elements of an array as arguments to a function.

const print = (first, second, third) => console.log(`first: "${first}" second: "${second}" third: "${third}"`);

const fruits = ["apple", "banana", "coconut"];
print(...fruits);
// first: "apple" second: "banana" third: "coconut"

We can also spread an array of numbers into the Math.max method. Isn't that neat?

const numbers = [1, 5, 23, -5, 2, 42];
Math.max(...numbers);

Next: Rest Elements

Want to split an array into head and tail?
Here we are destructuing an array and use ... (rest element) to collect all elements which are not already picked by the destructuring pattern into tail.

const [head, ...tail] = [1, 2, 3];
// head: 1
// tail: [2, 3]

Before, we already used ... to spread elements as arguments to a function on the call-site. Now we use ... (rest parameter) to collect all passed parameter into args.

function getNumberOfPassedArguments(...args) {
    return args.length;
}

getNumberOfPassedArguments("apple", "banana", "coconut");
// 3

Do you know compose from Ramda or Underscore? Look how short the implementation is:

const compose = (...fns) => x => fns.reduceRight((acc, cur) => cur(acc), x);

const fivePercentDiscount = price => price * 0.95;
const germanTax = price => price * 1.19;

const getProductPrice = compose(germanTax, fivePercentDiscount);
getProductPrice(10);

Let's get to objects. Here we can do pretty much the same: Spread Properties

With ... (spread properties) we can shallow copy all (own enumerable) properties (key and value) of an object.

const person = {name: "Thomas", age: 28};
const copiedPerson = {...person};

We can spread the properties and immediately overwrite properties during creation.

const thomas = {name: "Thomas", age: 28};
const peter = {...thomas, name: "Peter"};

We can overwrite an arbitrary number of properties if we already defined some properties and then spread properties of another object.

function initConfig(overwrites) {
    const defaults = {useSSL: true /*,  ... */};
    return {...defaults, ...overwrites};
}   

Want to have optinal properties on an object, but don't want to mutate the object after creation?
Here are to ways:

const shouldAddB = true;
let obj1 = {a:1, ...(shouldAddB && {b:2})};
// or
let obj2 = {a:1, ...(shouldAddB ? {b:2} : {})};

Rest Properties

We can also use ... (rest properties) to copy a subset of properties into an object. In the destructuring pattern we list the properties, which we don't want to have in the new object, and collect the remaining properties with the ... rest properties into personWithoutAge.



const {age, ...personWithoutAge} = {name: "Thomas", age: 28, country: "Germany"};
// personWithoutAge: {name: "Thomas", country: "Germany"}

Which one do you use in your code and do you have more use cases? Please let us know!

Top comments (0)