Skip to content
Advertisement

JavaScript – Any subtle differences with Array.from(set) vs Array.from(set.values())

I know both methods result in the same exact thing and are valid ways to create an Array from a Set.

But I’m curious if there’s any difference between and/or reason to use one method over the other when specifically utilizing the following methods:

const set = new Set(["first", "second", "third"]);

const arr1 = Array.from(set); // or [...set]

const arr2 = Array.from(set.values()); // or [...set.values()]

I’m guessing in this context, other than one method being slightly more verbose, there’s no difference. But am wondering if maybe there’s some very slight subtle less obvious “under the hood” difference(s) that might be less apparent/known.

Advertisement

Answer

When using a Set, assuming no built-in methods have been overwritten (which would be exceedingly strange), those methods are all identical.

Per the specification, when a value is spread into an array literal:

1. Let spreadRef be the result of evaluating AssignmentExpression.
2. Let spreadObj be ? GetValue(spreadRef).
3. Let iteratorRecord be ? GetIterator(spreadObj).
4. Repeat,
  a. Let next be ? IteratorStep(iteratorRecord).
  b. If next is false, return nextIndex.
  c. Let nextValue be ? IteratorValue(next).
  d. Perform ! CreateDataPropertyOrThrow(array, ! ToString(𝔽(nextIndex)), nextValue).
  e. Set nextIndex to nextIndex + 1.

It’s pretty simple: it calls the iterator of the value, and for each element returned by the iterator, it adds an element to the array at the appropriate index.

Set.prototype.values simply returns the Set iterator. So [...set] and [...set.values()] do the same thing, except that the latter extracts the iterator first.

The remaining difference to explore is Array.from vs spreading into an array. Spreading into an array just invokes the iterator. Array.from is a lot more complicated.

To summarize, Array.from on a value will:

  • Invoke the iterator on the value, if it exists
    • If the this value is a constructor, that will be called to construct a new instance; otherwise, a plain array is created
    • For each element returned by the iterator, the element will be set onto the new instance, and the instance’s length property will be incremented by one
    • If a mapper function (second argument) is provided, each element will instead be passed through the mapper before putting into the iterator
  • Otherwise, if the value isn’t iterable
    • Its length will be retrieved
    • If the this value is a constructor, that will be called to construct a new instance; otherwise, a plain array is created
    • Each index on the original object from 0 to the length just retrieved will be accessed, then put onto the new instance, possibly passing through the mapper function beforehand

But in the case of a Set, it just invokes the iterator, which results in the same output as the [...set].

As a note says:

The from function is an intentionally generic factory method; it does not require that its this value be the Array constructor. Therefore it can be transferred to or inherited by any other constructors that may be called with a single numeric argument.

That’s why the process for Array.from is a lot more complicated than spreading into an array literal.

Advertisement