2

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

gremo
  • 47,186
  • 75
  • 257
  • 421
  • https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax#only_for_iterables – Yousaf Jul 06 '22 at 10:12

3 Answers3

3

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 properties {...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 compared to the one described above for 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.

Nick Parsons
  • 45,728
  • 6
  • 46
  • 64
  • If I remember correctly, *spread properties* work the way they do to behave exactly like `Object.assign` – Felix Kling Jul 06 '22 at 12:08
  • @FelixKling Ah, that would make sense. Helps to keep things consistent :) (although there's still the difference between the two with regards to the setters being invoked on the target) – Nick Parsons Jul 06 '22 at 12:27
  • 1
    Thanks that's a great explanation. Yeah you right about the "operator" word, but you can read it everywhere searching :)) – gremo Jul 06 '22 at 13:50
  • No worries :) - Yep, it is a term that is misused quite a lot. I mainly pointed that out to highlight that what `...` "means" and does changes depending on its context – Nick Parsons Jul 06 '22 at 13:57
2

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

eroironico
  • 1,159
  • 1
  • 15
1

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'] : [])
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392