Skip to content

Commit

Permalink
Minor spread syntax rewrite (#18503)
Browse files Browse the repository at this point in the history
* Minor spread syntax rewrite

* Update files/en-us/web/javascript/reference/operators/spread_syntax/index.md

Co-authored-by: Jean-Yves Perrier <jypenator@gmail.com>

Co-authored-by: Jean-Yves Perrier <jypenator@gmail.com>
  • Loading branch information
Josh-Cena and teoli2003 authored Jul 19, 2022
1 parent 602c0ef commit 33f9b4e
Showing 1 changed file with 62 additions and 150 deletions.
212 changes: 62 additions & 150 deletions files/en-us/web/javascript/reference/operators/spread_syntax/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,79 +11,45 @@ browser-compat: javascript.operators.spread
---
{{jsSidebar("Operators")}}

**Spread syntax** (`...`) allows an iterable such as an array
expression or string to be expanded in places where zero or more arguments (for
function calls) or elements (for array literals) are expected, or an object expression
to be expanded in places where zero or more key-value pairs (for object literals) are
expected.
**Spread syntax** (`...`) allows an iterable, such as an array or string, to be expanded in places where zero or more arguments (for function calls) or elements (for array literals) are expected. In an object literal, the spread syntax enumerates the properties of an object and adds the key-value pairs to the object being created.

{{EmbedInteractiveExample("pages/js/expressions-spreadsyntax.html")}}

## Description

Spread syntax can be used when all elements from an object or array need to be included
in a list of some kind.

In the above example, the defined function takes `x`, `y`, and
`z` as arguments and returns the sum of these values. An array value is also
defined.

When we invoke the function, we pass it all the values in the array using the spread
syntax and the array name — `...numbers`.

If the array contained more than three numbers, e.g. `[1, 2, 3, 4]`, then it
would still work fine, except that all four would be passed, but only the first three
would be used unless you added more arguments to the function, e.g.:

```js
function sum(x, y, z, n) {
return x + y + z + n;
}
```
Spread syntax looks exactly like rest syntax. In a way, spread syntax is the opposite of rest syntax. Spread syntax "expands" an array into its elements, while rest syntax collects multiple elements and "condenses" them into a single element. See [rest parameters](/en-US/docs/Web/JavaScript/Reference/Functions/rest_parameters) and [rest property](/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#rest_property).

The above example is somewhat rigid; the real value in spread syntax is that it works
with the same value, no matter how many elements are contained in the object, array,
etc.
{{EmbedInteractiveExample("pages/js/expressions-spreadsyntax.html")}}

It is commonly used when you want to add a new item to a local data store, or display
all stored items plus a new addition. A very simple version of this kind of action
could look like so:
## Syntax

```js
let numberStore = [0, 1, 2];
let newNumber = 12;
numberStore = [...numberStore, newNumber];
myFunction(a, ...iterableObj, b)
[1, ...iterableObj, '4', 'five', 6]
({ ...obj, key: 'value' })
```

In the above example you can rerun the last line as many times as you like, to keep
adding an additional 12 to the end of the array.
## Description

## Syntax
Spread syntax can be used when all elements from an object or array need to be included in a new array or object, or should be applied one-by-one in a function call's arguments list. There are three distinct places that accept the spread syntax:

For function calls:
- [Function arguments](#spread_in_function_calls) list (`myFunction(a, ...iterableObj, b)`)
- [Array literals](#spread_in_array_literals) (`[1, ...iterableObj, '4', 'five', 6]`)
- [Object literals](#spread_in_object_literals) (`{ ...obj, key: 'value' }`)

```js
myFunction(...iterableObj); // pass all elements of iterableObj as arguments to function myFunction
```
Although the syntax looks the same, they come with slightly different semantics.

For array literals:
Only [iterable](/en-US/docs/Web/JavaScript/Reference/Iteration_protocols) objects, like {{jsxref("Array")}}, can be spread in array and function parameters. Many objects are not iterable, including all [plain objects](/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object) that lack a [`Symbol.iterator`](/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/iterator) method:

```js
[...iterableObj, '4', 'five', 6]; // combine two arrays by inserting all elements from iterableObj
```js example-bad
const obj = { key1: 'value1' };
const array = [...obj]; // TypeError: obj is not iterable
```

For object literals (new in ECMAScript 2018):
On the other hand, spreading in object literals [enumerates](/en-US/docs/Web/JavaScript/Enumerability_and_ownership_of_properties#traversing_object_properties) the own properties of the object. For typical arrays, all indices are enumerable own properties, so arrays can be spread into objects.

```js
let objClone = { ...obj }; // pass all key:value pairs from an object
const array = [1, 2, 3];
const obj = { ...array }; // { 0: 1, 1: 2, 2: 3 }
```

## Rest syntax (parameters)

Rest syntax looks exactly like spread syntax. In a way, rest syntax is the opposite of
spread syntax. Spread syntax "expands" an array into its elements, while rest syntax
collects multiple elements and "condenses" them into a single element. See
{{jsxref("Functions/rest_parameters", "rest parameters", "", 1)}}.
When using spread syntax for function calls, be aware of the possibility of exceeding the JavaScript engine's argument length limit. See {{jsxref("Function.prototype.apply()")}} for more details.

## Examples

Expand All @@ -95,68 +61,35 @@ It is common to use {{jsxref("Function.prototype.apply()")}} in cases where you
use the elements of an array as arguments to a function.

```js
function myFunction(x, y, z) { }
let args = [0, 1, 2];
function myFunction(x, y, z) {}
const args = [0, 1, 2];
myFunction.apply(null, args);
```

With spread syntax the above can be written as:

```js
function myFunction(x, y, z) { }
let args = [0, 1, 2];
function myFunction(x, y, z) {}
const args = [0, 1, 2];
myFunction(...args);
```

Any argument in the argument list can use spread syntax, and the spread syntax can be
used multiple times.

```js
function myFunction(v, w, x, y, z) { }
let args = [0, 1];
function myFunction(v, w, x, y, z) {}
const args = [0, 1];
myFunction(-1, ...args, 2, ...[3]);
```

#### Apply for new operator

When calling a constructor with {{jsxref("Operators/new", "new")}} it's not possible to
**directly** use an array and `apply()` (`apply()`
does a `[[Call]]` and not a `[[Construct]]`). However, an array
can be easily used with `new` thanks to spread syntax:

```js
let dateFields = [1970, 0, 1]; // 1 Jan 1970
let d = new Date(...dateFields);
```

To use `new` with an array of parameters without spread syntax, you would
have to do it **indirectly** through partial application:
When calling a constructor with {{jsxref("Operators/new", "new")}}, it's not possible to **directly** use an array and `apply()`, because `apply()` _calls_ the target function instead of _constructing_ it, which means, among other things, that [`new.target`](/en-US/docs/Web/JavaScript/Reference/Operators/new.target) will be `undefined`. However, an array can be easily used with `new` thanks to spread syntax:

```js
function applyAndNew(constructor, args) {
function partial () {
return constructor.apply(this, args);
};
if (typeof constructor.prototype === "object") {
partial.prototype = Object.create(constructor.prototype);
}
return partial;
}

function myConstructor () {
console.log("arguments.length: " + arguments.length);
console.log(arguments);
this.prop1="val1";
this.prop2="val2";
};

let myArguments = ["hi", "how", "are", "you", "mr", null];
let myConstructorWithArguments = applyAndNew(myConstructor, myArguments);

console.log(new myConstructorWithArguments);
// (internal log of myConstructor): arguments.length: 6
// (internal log of myConstructor): ["hi", "how", "are", "you", "mr", null]
// (log of "new myConstructorWithArguments"): {prop1: "val1", prop2: "val2"}
const dateFields = [1970, 0, 1]; // 1 Jan 1970
const d = new Date(...dateFields);
```

### Spread in array literals
Expand All @@ -170,8 +103,8 @@ instead using a combination of {{jsxref("Array.prototype.push", "push()")}},
"concat()")}}, etc. With spread syntax this becomes much more succinct:

```js
let parts = ['shoulders', 'knees'];
let lyrics = ['head', ...parts, 'and', 'toes'];
const parts = ['shoulders', 'knees'];
const lyrics = ['head', ...parts, 'and', 'toes'];
// ["head", "shoulders", "knees", "and", "toes"]
```

Expand All @@ -181,22 +114,19 @@ literal, and may be used more than once.
#### Copy an array

```js
let arr = [1, 2, 3];
let arr2 = [...arr]; // like arr.slice()
const arr = [1, 2, 3];
const arr2 = [...arr]; // like arr.slice()

arr2.push(4);
// arr2 becomes [1, 2, 3, 4]
// arr remains unaffected
```

> **Note:** Spread syntax effectively goes one level deep while copying
> an array. Therefore, it may be unsuitable for copying multidimensional arrays, as
> the following example shows. (The same is true with {{jsxref("Object.assign()")}}
> and spread syntax.)
> **Note:** Spread syntax effectively goes one level deep while copying an array. Therefore, it may be unsuitable for copying multidimensional arrays. The same is true with {{jsxref("Object.assign()")}} — no native operation in JavaScript does a deep clone.
>
> ```js example-bad
> let a = [[1], [2], [3]];
> let b = [...a];
> const a = [[1], [2], [3]];
> const b = [...a];
>
> b.shift().shift();
> // 1
Expand All @@ -213,32 +143,31 @@ of an existing array. Without spread syntax, this is done as:
```js
let arr1 = [0, 1, 2];
let arr2 = [3, 4, 5];
const arr2 = [3, 4, 5];
// Append all items from arr2 onto arr1
// Append all items from arr2 onto arr1
arr1 = arr1.concat(arr2);
```
With spread syntax this becomes:

```js
let arr1 = [0, 1, 2];
let arr2 = [3, 4, 5];
const arr2 = [3, 4, 5];

arr1 = [...arr1, ...arr2];
// arr1 is now [0, 1, 2, 3, 4, 5]
// Note: Not to use const otherwise, it will give TypeError (invalid assignment)
// arr1 is now [0, 1, 2, 3, 4, 5]
```

{{jsxref("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:

```js
let arr1 = [0, 1, 2];
let arr2 = [3, 4, 5];
const arr1 = [0, 1, 2];
const arr2 = [3, 4, 5];

// Prepend all items from arr2 onto arr1
Array.prototype.unshift.apply(arr1, arr2)
Array.prototype.unshift.apply(arr1, arr2);

// arr1 is now [3, 4, 5, 0, 1, 2]
```
Expand All @@ -247,38 +176,39 @@ With spread syntax, this becomes:

```js
let arr1 = [0, 1, 2];
let arr2 = [3, 4, 5];
const arr2 = [3, 4, 5];

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

> **Note:** Unlike `unshift()`, this creates a new
> `arr1`, and does not modify the original `arr1` array
> in-place.
> **Note:** Unlike `unshift()`, this creates a new `arr1`, instead of modifying the original `arr1` array in-place.
### Spread in object literals

The [Rest/Spread Properties for ECMAScript](https://github.com/tc39/proposal-object-rest-spread) proposal (ES2018)
added spread properties to {{jsxref("Operators/Object_initializer", "object literals", 1)}}.
It copies own enumerable properties from a provided object onto a new object.
Shallow-cloning (excluding prototype) or merging of objects is now possible using a
shorter syntax than {{jsxref("Object.assign()")}}.
Shallow-cloning (excluding prototype) or merging of objects is possible using a shorter syntax than {{jsxref("Object.assign()")}}.

```js
let obj1 = { foo: 'bar', x: 42 };
let obj2 = { foo: 'baz', y: 13 };
const obj1 = { foo: 'bar', x: 42 };
const obj2 = { foo: 'baz', y: 13 };

let clonedObj = { ...obj1 };
const clonedObj = { ...obj1 };
// Object { foo: "bar", x: 42 }

let mergedObj = { ...obj1, ...obj2 };
const mergedObj = { ...obj1, ...obj2 };
// Object { foo: "baz", x: 42, y: 13 }
```

Note that {{jsxref("Object.assign()")}} can be used to mutate an object, whereas spread syntax can't.

```js
const obj1 = { foo: 'bar', x: 42 };
Object.assign(obj1, { x: 1337 });
console.log(obj1); // Object { foo: "bar", x: 1337 }
```

In addition, {{jsxref("Object.assign()")}} triggers setters on the target object, whereas spread syntax does not.

```js
const objectAssign = Object.assign({ set foo(val) { console.log(val); } }, { foo: 1 });
// Logs "1"; objectAssign.foo is still the original setter
Expand Down Expand Up @@ -312,25 +242,6 @@ const mergedObj1 = merge(obj1, obj2);
// Object { foo: 'baz', x: 42, y: 13 }
```

### Only for iterables
Spread syntax (other than in the case of spread properties) can only be applied to [iterable](/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/iterator)
objects like {{jsxref("Array")}}, or with iterating functions such as `map()`, `reduce()`, and `assign()`.
Many objects are not iterable, including {{JSxRef("Object")}}:
```js
let obj = {'key1': 'value1'};
let array = [...obj]; // TypeError: obj is not iterable
```
To use spread syntax with these objects, you will need to provide an iterator function.
### Spread with many values
When using spread syntax for function calls, be aware of the possibility of exceeding the JavaScript engine's argument length limit.
See {{jsxref("Function.prototype.apply", "apply()")}} for more details.
## Specifications

{{Specifications}}
Expand All @@ -341,5 +252,6 @@ See {{jsxref("Function.prototype.apply", "apply()")}} for more details.

## See also

- {{jsxref("Functions/rest_parameters", "Rest parameters", "", 1)}} (also '`...`')
- {{jsxref("Function.prototype.apply()")}} (also '`...`')
- [Rest parameters](/en-US/docs/Web/JavaScript/Reference/Functions/rest_parameters)
- [Rest property](/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#rest_property)
- {{jsxref("Function.prototype.apply()")}}

0 comments on commit 33f9b4e

Please sign in to comment.