22

Why can't spread operator be used multiple times?

let arr = [[[1, 2, 3]]];

console.log(arr); // Array [ Array[1] ]
console.log(...arr); // Array [ Array[3] ]
console.log(...(...arr));
// SyntaxError: expected '=>' after argument list, got ')'

I would expect:

console.log(...(...arr)); // Array [ 1, 2, 3 ]
hippietrail
  • 15,848
  • 18
  • 99
  • 158
madox2
  • 49,493
  • 17
  • 99
  • 99
  • `...(arr)` works. it is the same as `...arr` – madox2 Jan 26 '16 at 17:23
  • 1
    According to the ES6 spec when the spread operator is encountered an iterator is created from a spread object that is the result of evaluating the assignment expression which is in your working example `arr` e.g. `...arr`. With `console.log(...(...arr));` you are attempting to pass a spread operator + assignment expression as an assignment expression to another spread operator. See http://www.ecma-international.org/ecma-262/6.0/#sec-argument-lists – br3w5 Jan 26 '16 at 17:44
  • You might want to try `console.log(...[].concat(...arr))` (or any other `flatten`ing function) – Bergi Jan 26 '16 at 19:15
  • Related: [Spread Syntax vs Rest Parameter in ES2015 / ES6](https://stackoverflow.com/q/33898512). – Henke May 10 '21 at 13:49

4 Answers4

18

Why can't spread operator be used multiple times?

... is not an operator. (...arr) is not valid JavaScript. ... is only allowed inside array literals and in arguments lists, but those are special forms of the syntax (notice the ... in the production rules below).

ArrayLiteral

ArrayLiteral :
  [ Elision_opt ]
  [ ElementList ]
  [ ElementList , Elision_opt ]

ElementList :
  Elision_opt SpreadElement
  ElementList , Elision_opt SpreadElement

SpreadElement:
  ... AssignmentExpression

Arguments

Arguments :
  ( )
  ( ArgumentList )

ArgumentList :
  AssignmentExpression
  ... AssignmentExpression
  ArgumentList , AssignmentExpression
  ArgumentList , ... AssignmentExpression
Felix Kling
  • 795,719
  • 175
  • 1,089
  • 1,143
  • I was convinced that spreading an already spread array would not evaluate, but turns out I was wrong, because it does. This seems to be the only right answer. +1 – html_programmer Jan 26 '16 at 18:21
  • 1
    @madox2: Yes. I don't know who came up with the term "spread operator", but it's certainly not an operator in the JavaScript sense. It's just a token. – Felix Kling Jan 26 '16 at 22:38
  • @FelixKling could you explain it little bit more? I went through your answer many times but I don't get it. – madox2 Jan 26 '16 at 23:02
  • You might like to support the question I've asked to gain clarification of the term "operator": [What, actually, is and is not an operator?](http://stackoverflow.com/questions/35029217) – hippietrail Jan 27 '16 at 14:22
  • 2
    @madox2: If we consider operators a special form of functions (which it is in other languages), then we could say that we can use an operator everywhere were we can use a function call. E.g. instead of `var foo = add(1, 2);` we can write `var foo = 1 + 2;`. However, we cannot replace `var foo = spread(arr);` with `var foo = ...arr;`. There is no such thing as a standalone spread operator, it is simply an extension of the syntax of array initializers and argument lists. And that becomes clear when looking at the language grammar. – Felix Kling Jan 27 '16 at 14:39
  • @madox2: And there's the minor detail you won't find "rest" or "spread" listed in the specification as operators. :-) Because as Felix says, they're much more constrained than operators, and they do something that something that's strict an operator couldn't do (like `foo(...arrray)`; there's no way for an operator to result in a value that would be passed as *discrete* arguments to `foo` like that does). – T.J. Crowder Aug 09 '16 at 15:52
3

According to this, spread syntax input is an iterable (e.g. array), but its produce output which is non-iterable (e.g. non-array). So the problem is that in outer spread syntax ... as input you put non-iterable thing (...arr) which cause SyntaxError. To flat you array you can use flat (if you put Infinity instead 2, then you will flat any nested array)

arr.flat(2)

let arr = [[[1, 2, 3]]];
console.log(arr.flat(2));

let arr2 = [[1,2,[3,4,[5,[6]]]], [[7,[8]],9]];;
console.log(arr2.flat(Infinity));
Kamil Kiełczewski
  • 85,173
  • 29
  • 368
  • 345
1

Because ...arr isn't like a function that returns a value in normal scenarios (you can test this by just typing ...[[1,2,3]] in console, if ... operated like a normal function we would expect a return of [1 2 3]. For that reason you can't chain spreads. From MDN:

The spread operator allows an expression to be expanded in places where multiple arguments (for function calls) or multiple elements (for array literals) are expected.

Ergo, spreads need to happen within array literals, object literals (if using obj spread, which is ES7) or within function calls So you could do console.log(...[].concat(...arr))

Matsemann
  • 21,083
  • 19
  • 56
  • 89
0

Not answer your question, but - in general, the three dots ... syntax can be applied to an iterable object - an array or an array-like object.

Why can't spread operator be used multiple times?

I will not answer the question Why.
But the snippet below shows that ...[].concat(...arr) achieves what you expected to get by doing ...(...arr). It is inspired by this comment.

const arr = [[[1, 2, 3]]];

console.log(JSON.stringify(arr));                  // [[[1,2,3]]]
console.log(JSON.stringify(...arr));               // [[1,2,3]]
// console.log(JSON.stringify(...(...arr)));       // SyntaxError: expect...
console.log(JSON.stringify(...[].concat(...arr))); // [1,2,3]
console.log(...[].concat(...[].concat(...arr)));   // 1 2 3
.as-console-wrapper { max-height: 100% !important; top: 0; }
Henke
  • 4,445
  • 3
  • 31
  • 44