简体   繁体   中英

JavaScript spread operator and conditional, why it doesn't work for arrays?

This is something I can't get.

With object, all works fine with both true and false . The expression (false && { teacher: 2 }) is obviously false , the spread operator simply doesn't complain:

console.log({
  ...(true && { foo: 'bar' }),
  ...(false && { bar: 'baz' }),
});

Result { foo: 'bar' } .

On the other hand, this is not working with arrays, or to say it better, is working only with true condition:

console.log([
  'foo',
  ...(true && ['bar']),
  ...(false && ['baz']),
]);

TypeError: (false && ["baz"]) is not iterable

Ok so according to the docs the spread operator initially worked only with iterables(such as arrays, objects are not iterable), the thing that you can use it with objects it's because they added this feature later in time but in fact the spread operator used with objects is a "wrapper" of Object.assign .

Said that, in your case:

Case with objects:

console.log({
  ...(true && { foo: 'bar' }),
  ...(false && { bar: 'baz' }),
});

Works because basically, on the 'falsy' condition it's doing:

console.log(Object.assign({}, false)) // Output: { }

Case with arrays:

console.log([
  'foo',
  ...(true && ['bar']),
  ...(false && ['baz']),
]);

In this case doesn't work because on the 'falsy' condition js will search for a iterable method on false which of course doesn't exists so it raises the error

To add to the already provided answers, spread is not an operator (as you've mentioned in your question), but rather, it is a syntax. This matters because ...x doesn't produce a value like operators such as + and - do, and it can't be used standalone - it needs to be used in certain contexts, such as in arrays [...x] or objects {...x} . Andrew Li goes into great detail about this in his great answer .

So while they look the same, the behaviour and runtime semantics of ...x depends on the context (ie: where it's being used). In fact, as Nick rightly points out in their answer , object spread/spread properties {...x} and array spread [...x] were added in two completely separate versions of the ECMAScript (JS) specification (spread properties being introduced in ECMAScript2018 and array spread being introduced in ECMAScript2015, aka ES6).


Spread propertis {...x} :

When we use the spread syntax in an object literal {...x} , the requirement is that the expression to the right-hand side of the ... (ie: x ) evaluates to an object or to something that can be converted to an object (with exceptions for null and undefined ). The (enumerable own) properties of this object are then copied into the object literal {} we're creating. When you end up doing:

{...false}

JavaScript sees that false is not an object (it's a primitive), so it converts it to one by wrapping it in a boolean object:

{...new Boolean(false))}

Since new Boolean(false) doesn't have any own-enumerable properties, the new object we're creating {} doesn't get any properties copied into it, so you end up with an empty object.


Array spread [...x] :

Using ...x (spread element) in the context of an array [...x] runs a different algorithm for spreading behavior as it does to spread properties {...x} .

With array spread, the requirement is that the expression to the right-hand side of the ... evaluates to something iterable.

In this context, something is iterable if it's an object that has the property/well-known symbol of Symbol.iterator defined on it that when invoked, returns an iterator . Arrays natively have this symbol which is why we can use ...[] on them in the context of arrays, hence why your second example works with true && ['bar'] . Each value that the iterator produces is added to the newly created array that the spread syntax is being used in.

When x in [...x] evaluates to something non-iterable such as false in your example, JS is unable to iterate it, and so you get a TypeError.

The first one spreads into an object

It copies own enumerable properties from a provided object

and it works for any type of variable.

 console.log({ ...true }); console.log({ ...false }); console.log({ ...undefined }); console.log({ ...null }); console.log({ ...5 }); console.log({ ...'' });

The second approach spreads into an array and this needs an iterable . false is not iterable.

To overcome this, you could take a conditional operator and use an empty array for spreading.

...(condition ? ['bar'] : [])

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM