As the title says, why does for-of
run the loop body with the loop variable bound to undefined
for indices not in the Array
, while other iteration constructs (forEach()
, for-in
, etc.) don't?
It is not about:
TypedArray
sArray
(every other method seems to work in the expected way)undefined
elementsArray
Is the following informal description found on MDN incorrect?
The
for...of
statement [...] invokes a custom iteration hook with statements to be executed for the value of each distinct property of the object.
I.e. it is also invoked for non-existent properties.
const sparse = [0, 1, 2] // Was [0, , 2], but some are unfamiliar with this syntax
// or think it creates the array [0, undefined, 2]
delete sparse[1]
for (let e of sparse) console.log('for-of', e)
// Contrast with:
sparse.forEach(e => console.log('forEach', e))
for (let i in sparse) console.log('for-in', sparse[i])
console.log('map', sparse.map(e => e)) // Note, prints incorrectly in the snippet
// console, check browser console
// etc.
Is this the intended behaviour (Yes) & why was it designed in this way?
for..of
calls the Array iterator method, which is described in the spec.
(2) Let iterator be ObjectCreate(%ArrayIteratorPrototype%, «[[IteratedObject]], [[ArrayIteratorNextIndex]], [[ArrayIterationKind]]»).
(4) Set iterator’s [[ArrayIteratorNextIndex]] internal slot to 0.
Then, when the iterator is iterated over, in 22.1.5.2.1 %ArrayIteratorPrototype%.next::
(6) Let index be the value of the [[ArrayIteratorNextIndex]] internal slot of O.
(10) If index ≥ len, then
(10) (a) Set the value of the [[IteratedObject]] internal slot of O to undefined.
(10) (b) Return CreateIterResultObject(undefined, true).
(11) Set the value of the [[ArrayIteratorNextIndex]] internal slot of O to index+1.
(create iterator result object whose value is
array[index]
)
In other words - the iterator iterates starting at index 0, and increments the index by 1 every time .next()
is called. It does not check to see if the array actually has an item at that index (which a sparse array will not) - it just checks that the index is less than the .length
of the array.
With for..in
, on the other hand, all enumerable properties are iterated over, and an array's own enumerable properties do not include sparse array indicies.
const sparse = [0, , 2];
console.log(sparse.hasOwnProperty('0'));
console.log(sparse.hasOwnProperty('1'));
So yes, this is intended behavior.