Spreading undefined in array vs object
Asked Answered
F

3

82

Why does spreading undefined in an object return an empty object? {...undefined} // equals {}:

console.log({...undefined})

And Why does spreading undefined in an array give you an error? [...undefined] // type error:

console.log([...undefined])
Forenamed answered 7/11, 2017 at 10:9 Comment(5)
github.com/tc39/ecma262/issues/687Ethelstan
To summarize: object spread is equivalent to Object.assign whereas array spread is actually iterable spread and undefined isn't iterable.Sterlingsterlitamak
github.com/tc39/proposal-object-rest-spread/issues/45Alumina
This is also a good discussion: github.com/tc39/ecma262/pull/1069Loudmouthed
Also ...null and ...false seem to behave the same?Vale
C
104

As noted in the comments, and summarized by @ftor from #687, object spread is equivalent1 to Object.assign() (issues #687, #45), whereas spread in array literal context is iterable spread.

Quoting Ecma-262 6.0, Object.assign() is defined as:

19.1.2.1 Object.assign ( target, ...sources )

The assign function is used to copy the values of all of the enumerable own properties from one or more source objects to a target object. When the assign function is called, the following steps are taken:

  1. Let to be ToObject(target).
  2. ReturnIfAbrupt(to).
  3. If only one argument was passed, return to.
  4. Let sources be the List of argument values starting with the second argument.
  5. For each element nextSource of sources, in ascending index order, do
    1. If nextSource is undefined or null, let keys be an empty List.
    2. Else, ...

...followed by the description of copying own properties. The draft of Object Rest/Spread Properties is here. It is not a part of the Ecma-262 6.0.

A SpreadElement in an array literal expression is defined to begin as follows:

SpreadElement : ... AssignmentExpression

  1. Let spreadRef be the result of evaluating AssignmentExpression.
  2. Let spreadObj be GetValue(spreadRef).
  3. Let iterator be GetIterator(spreadObj).
  4. ReturnIfAbrupt(iterator).

And since undefined does not have a property with the key @@iterator, a TypeError is thrown, based on the steps of GetIterator. The standard is not an easy read, but if I'm not mistaken, the path to error is GetIterator -> GetMethod -> GetV -> ToObject, which throws a TypeError for undefined and null.

A simple remedy to using variables with possibly undefined value in array initialization is to use a default:

const maybeArray = undefined;
const newArray = [ ...(maybeArray || []) ];

1: There is a difference in how setters are handled.

Casanova answered 7/11, 2017 at 11:30 Comment(2)
Since undefined and null are treated as an empty enumerable, why aren't they also treated as an empty iterable?Wellrounded
You can find discussion about that in github.com/tc39/ecma262/issues/687.Ethelstan
S
18

Normally, uses of ...x requires x to be iterable because the point of ... is normally to flatten an iterable into its components. An array is a prime example of an iterable.

undefined is not iterable. It has no components, so it doesn't make sense to iterate over undefined. [...undefined] therefore fails. for (const e of undefined) similarly fails, since of also requires an iterable.

However, {...x} requires x to be enumerable because it needs not just values, but keys along with them. An object is a prime example of an enumerable.

undefined is enumerable. undefined is sometime treated as an object, and this is one of those cases. Objects are enumerable because they can have properties. {...undefined} therefore succeeds. for (const p in undefined) similarly succeeds, since in requires an enumerable.


A note about null

A question asking about null's behaviour as an operand of ... was closed as a duplicate of this question. While null and undefined have distinct types, the above answers applies equally to null.

Sidell answered 13/7, 2020 at 23:32 Comment(7)
Since undefined and null are treated as an empty enumerable, why aren't they also treated as an empty iterable?Wellrounded
@Géry Ogam, Are they? Or are they treated like objects (which aren't normally iterable).Sidell
In the accepted answer, the quoted standard asserts ‘If nextSource is undefined or null, let keys be an empty List.’ so they behave like the empty object in the context of object spread. But I don't see what would prevent them from behaving like empty lists in other contexts like array spread. Restricting their polymorphism creates an inconsistency that surprises developers.Wellrounded
@Géry Ogam, Re "Restricting their polymorphism creates an inconsistency", An inconsistency with what? It's currently consistent with objects. With what would undefined become consistent if it was both iterable and enumerable? Anyway, my earlier question was rhetorical, pointing out that your point of view was purely subjective. And also completely off-topic. This is not the place the start a debate as to how this should have been designed. (If it was, I'd question why undefined is enumerable at all.)Sidell
Okay you can argue that undefined and null behave like an empty object in this context so it’s consistent that they don’t behave like an empty array in another context. But that’s not developers’ expectation as the number of likes of the question shows, so there is clearly a language issue. undefined and null represent emptiness without a specific type, they don't represent only empty objects, so they should be usable in any contexts where an empty value is accepted. That's what developers expect.Wellrounded
@Géry Ogam, Re "But that’s not developers’ expectation as the number of likes of the question shows", huh? That's a mighty bold claim! /// Re "undefined and null represent emptiness", Hell no! They are an exceptional value, but not necessarily emptiness. /// Re "That's what developers expect." Not really! I would expect an error.Sidell
Re "Okay you can argue", That's my point. One can argue. That's the answer to your question. Now please stop. You were answered three times now. This is not a place for debate.Sidell
T
1

You may use this expression: [...(this.options || [])], where options is an array.

Tinner answered 11/1, 2022 at 13:19 Comment(1)
This in no way answers the question. not having sufficient permissions to post comments is not an excuse for posting it as an answer.Sidell

© 2022 - 2024 — McMap. All rights reserved.