One of the most interesting features added to ES2015 was the spread operator. This operator makes copying and merging arrays a lot simpler. Rather than calling the concat()
or slice()
method, you could use the ...
operator:
const arr1 = [10, 20, 30];
const copy = [...arr1];
console.log(copy);
const arr2 = [40, 50];
const merge = [...arr1, ...arr2];
console.log(merge);
The spread operator also comes in handy in situations where an array must be passed in as separate arguments to a function. For example:
const arr = [10, 20, 30]
console.log(Math.max(...arr));
ES2018 further expands this syntax by adding spread properties to object literals. With the spread properties you can copy own enumerable properties of an object onto a new object. Consider the following example:
const obj1 = {
a: 10,
b: 20
};
const obj2 = {
...obj1,
c: 30
};
console.log(obj2);
In this code, the ...
operator is used to retrieve the properties of obj1
and assign them to obj2
. Prior to ES2018, attempting to do so would throw an error. If there are multiple properties with the same name, the property that comes last will be used:
const obj1 = {
a: 10,
b: 20
};
const obj2 = {
...obj1,
a: 30
};
console.log(obj2);
Spread properties also provide a new way to merge two or more objects, which can be used as an alternative to the Object.assign()
method:
const obj1 = {a: 10};
const obj2 = {b: 20};
const obj3 = {c: 30};
console.log({...obj1, ...obj2, ...obj3});
console.log(Object.assign({}, obj1, obj2, obj3));
Note, however, that spread properties do not always produce the same result as Object.assign()
. Consider the following code:
Object.defineProperty(Object.prototype, 'a', {
set(value) {
console.log('set called!');
}
});
const obj = {a: 10};
console.log({...obj});
console.log(Object.assign({}, obj));
In this code, the Object.assign()
method executes the inherited setter property. Conversely, the spread properties simply ignore the setter.
It's important to remember that spread properties only copy enumerable properties. In the following example, the type
property won’t show up in the copied object because its enumerable
attribute is set to false
:
const car = {
color: 'blue'
};
Object.defineProperty(car, 'type', {
value: 'coupe',
enumerable: false
});
console.log({...car});
Inherited properties are ignored even if they are enumerable:
const car = {
color: 'blue'
};
const car2 = Object.create(car, {
type: {
value: 'coupe',
enumerable: true,
}
});
console.log(car2.color);
console.log(car2.hasOwnProperty('color'));
console.log(car2.type);
console.log(car2.hasOwnProperty('type'));
console.log({...car2});
In this code, car2
inherits the color
property from car
. Because spread properties only copy the own properties of an object, color
is not included in the return value.
Keep in mind that spread properties can only make a shallow copy of an object. If a property holds an object, only the reference to the object will be copied:
const obj = {x: {y: 10}};
const copy1 = {...obj};
const copy2 = {...obj};
console.log(copy1.x === copy2.x);
The x
property in copy1
refers to the same object in memory that x
in copy2
refers to, so the strict equality operator returns true
.
Another useful feature added to ES2015 was rest parameters, which enabled JavaScript programmers to use ...
to represent values as an array. For example:
const arr = [10, 20, 30];
const [x, ...rest] = arr;
console.log(x);
console.log(rest);
Here, the first item in arr
is assigned to x
, and remaining elements are assigned to the rest
variable. This pattern, called array destructuring, became so popular that the Ecma Technical Committee decided to bring similar functionality to objects:
const obj = {
a: 10,
b: 20,
c: 30
};
const {a, ...rest} = obj;
console.log(a);
console.log(rest);
This code uses the rest properties in a destructuring assignment to copy the remaining own enumerable properties into a new object. Note that rest properties must always appear at the end of the object, otherwise an error is thrown:
const obj = {
a: 10,
b: 20,
c: 30
};
const {...rest, a} = obj;
Also keep in mind that using multiple rest syntaxes in an object causes an error unless they are nested:
const obj = {
a: 10,
b: {
x: 20,
y: 30,
z: 40
}
};
const {b: {x, ...rest1}, ...rest2} = obj;
const {...rest, ...rest2} = obj;