An Array is an exotic object: its [[DefineOwnProperty]] treats array-index keys (integers from 0 to 2³²−2) specially. Elements are ordinary properties, and the non-configurable length property is always greater than the highest index. Two rules follow:
- Setting an index at or beyond
lengthraiseslengthtoindex + 1. - Setting
lengthlower deletes the elements at or above it. Raising it creates no properties.
A hole (empty slot) is an index below length with no own property. It comes from [1, , 3], new Array(3), arr[5] = x past the end, delete arr[i], and raising length. [1, undefined, 3] is different: 1 in arr is true there and false for a hole.
Methods differ in how they treat holes:
- Skip them (
HasPropertycheck):forEach,map(the hole is kept in the result),filter,reduce,some,every,indexOf,lastIndexOf,flat,sort(moves holes to the end),Object.keys,for...in. - Read as
undefined(Getonly):includes,find,findIndex,findLast,join(empty string), spread,for...of,Array.from,entries/keys/values, and the new copying methods such astoSorted.
Because the check is HasProperty, a hole is visible if the prototype chain has that index, which only matters if someone assigned Array.prototype[1].
JSON.stringify writes holes as null.
Engine note (V8, not spec): arrays with holes are tracked as "holey" and stay that way, so their operations need extra checks. For performance, build arrays with literals or push rather than new Array(n).