This blog post describes how ECMAScript 6 handles holes in Arrays.

Holes in Arrays #

Holes are indices “inside” an Array that have no associated element. In other words: An Array arr is said to have a hole at index i if:

0 ≤ i < arr.length

< !(i in arr)

For example: The following Array has a hole at index 1.

> let arr = ['a',,'b'] 'use strict' > 0 in arr true > 1 in arr false > 2 in arr true > arr[1] undefined

For more information, consult Sect. “Holes in Arrays” in “Speaking JavaScript”.

ECMAScript 6: holes are treated like undefined elements #

The general rule for Array methods that are new in ES6 is: each hole is treated as if it were the element undefined . Examples:

> Array.from(['a',,'b']) [ 'a', undefined, 'b' ] > [,'a'].findIndex(x => true) 0 > [...[,'a'].entries()] [ [ 0, undefined ], [ 1, 'a' ] ]

The idea is to steer people away from holes and to simplify long-term. Unfortunately that means that things are even more inconsistent now.

Array operations and holes #

Array.from() converts holes to undefined :

> Array.from(['a',,'b']) [ 'a', undefined, 'b' ]

With a second argument, it works mostly like map() , but does not ignore holes:

> Array.from(new Array(3), (x,i) => i) [ 0, 1, 2 ]

Spread operator ( ... ) #

Inside Arrays, the spread operator ( ... ) works much like Array.from() (but its operand must be iterable, whereas Array.from() can handle anything that’s Array-like).

> [...['a',,'b']] [ 'a', undefined, 'b' ]

In ECMAScript 5, behavior already varied slightly. For example:

forEach() , filter() , every() and some() ignore holes.

, , and ignore holes. map() skips but preserves holes.

skips but preserves holes. join() and toString() treat holes as if they were undefined elements, but interprets both null and undefined as empty strings.

ECMAScript 6 adds new kinds of behaviors:

copyWithin() creates holes when copying holes (i.e., it deletes elements if necessary).

creates holes when copying holes (i.e., it deletes elements if necessary). entries() , keys() , values() treat each hole as if it was the element undefined .

, , treat each hole as if it was the element . find() and findIndex() do the same.

and do the same. fill() doesn’t care whether there are elements at indices or not.

The following table describes how Array.prototype methods handle holes.

Method Holes are concat Preserved ['a',,'b'].concat(['c',,'d']) → ['a',,'b','c',,'d'] copyWithin ✓ Preserved [,'a','b',,].copyWithin(2,0) → [,'a',,'a'] entries ✓ Elements [...[,'a'].entries()] → [[0,undefined], [1,'a']] every Ignored [,'a'].every(x => x==='a') → true fill ✓ Filled new Array(3).fill('a') → ['a','a','a'] filter Removed ['a',,'b'].filter(x => true) → ['a','b'] find ✓ Elements [,'a'].find(x => true) → undefined findIndex ✓ Elements [,'a'].findIndex(x => true) → 0 forEach Ignored [,'a'].forEach((x,i) => log(i)); → 1 indexOf Ignored [,'a'].indexOf(undefined) → -1 join Elements [,'a',undefined,null].join('#') → '#a##' keys ✓ Elements [...[,'a'].keys()] → [0,1] lastIndexOf Ignored [,'a'].lastIndexOf(undefined) → -1 map Preserved [,'a'].map(x => 1) → [,1] pop Elements ['a',,].pop() → undefined push Preserved new Array(1).push('a') → 2 reduce Ignored ['#',,undefined].reduce((x,y)=>x+y) → '#undefined' reduceRight Ignored ['#',,undefined].reduceRight((x,y)=>x+y) → 'undefined#' reverse Preserved ['a',,'b'].reverse() → ['b',,'a'] shift Elements [,'a'].shift() → undefined slice Preserved [,'a'].slice(0,1) → [,] some Ignored [,'a'].some(x => x !== 'a') → false sort Preserved [,undefined,'a'].sort() → ['a',undefined,,] splice Preserved ['a',,].splice(1,1) → [,] toString Elements [,'a',undefined,null].toString() → ',a,,' unshift Preserved [,'a'].unshift('b') → 3 values ✓ Elements [...[,'a'].values()] → [undefined,'a']

Notes:

ES6 methods have checkmarks (✓).

JavaScript ignores a trailing comma in an Array literal: ['a',,].length → 2

Helper function used in the table: const log = console.log.bind(console);

With regard to holes in Arrays, the only rule is now that there are no rules. Therefore, you should avoid holes if you can (they affect performance negatively, too). If you can’t then the table in the previous section may help.

Further reading #