What makes an empty object qualify as an Iterable or Array-like object?

51 views Asked by At

Given Array.from takes an iterable or array-like object as it's first argument, why does it not throw an error when passed an empty object, like this:

let emptyObject = Array.from( {} )

At iterable is defined as an object that implements the iterable protocol or the iterator protocol. An empty object seemingly does neither:

let emptyObject = {}

console.log(emptyObject[Symbol.iterator])
console.log(emptyObject['next'])
console.log(Array.from(emptyObject))

An Array-like object has a length property and can be indexed (ref). An empty object does not have a length property.

let emptyObject = {}

console.log(emptyObject['length'])
console.log(Array.from(emptyObject))

How does an empty object qualify?

3

There are 3 answers

5
VLAZ On BEST ANSWER

Array.from() works with array-like objects. More specifically it only really needs the length property:

console.log( Array.from({length: 5}) ); //array with 5 slots

This is defined in starting at step 7. of the Array.from algorithm

23.1.2.1 Array.from ( items [ , mapfn [ , thisArg ] ] )

[...]

  1. Let arrayLike be ! ToObject(items).
  2. Let len be ? LengthOfArrayLike(arrayLike).

[...]

LengthOfArrayLike is only this step:

7.3.18 LengthOfArrayLike ( obj )

[...]

Return ℝ(? ToLength(? Get(obj, "length"))).

Which means to retrieve the value of the length property of an object and defer to ToLength to convert it to a number. ToLength is defined as the following:

7.1.20 ToLength ( argument )

[...]

  1. Let len be ? ToIntegerOrInfinity(argument).
  2. If len ≤ 0, return +0.
  3. Return (min(len, 253 - 1)). Therefore a missing length means argument is undefined which at step 1. would be converted to zero.
1
Alexander Nenashev On

Seems it create just an empty array for values not qualified as iterable or array like except null and undefined since no iterator can be read from them (you can read an iterator from non-nullish values, it just will be undefined). So with no iterator fetched just an empty array is created.

console.log(Array.from(1));
console.log(Array.from(false));
console.log(Array.from(null));

0
Hao Wu On

When passing a non-empty argument, it seems it checks [Symbol.iterator] first, if it doesn't exist, it then checks length property. If neither of them exist, fallback to an empty array.

// Neither `length` or `[Symbol.iterator]` exist, empty array
const arr1 = Array.from({});
console.log('arr1', arr1);

const arr2 = Array.from({ length: 2 });
console.log('arr2', arr2);

const arr3 = Array.from({ *[Symbol.iterator]() { yield* [1, 2, 3] } });
console.log('arr3', arr3);

// Both `length` and `[Symbol.iterator]` exist, using `[Symbol.iterator]`
const arr4 = Array.from({ length: 2, *[Symbol.iterator]() { yield* [1, 2, 3] } });
console.log('arr4', arr4);