ECMAScript 6: holes in Arrays


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 methods and holes



Array.from()

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' ]

Array.prototype methods

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



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

  • map() 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).

  • entries(), keys(), values() treat each hole as if it was the element undefined.

  • find() and findIndex() 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.



















































































































































MethodHoles are
concatPreserved['a',,'b'].concat(['c',,'d']) → ['a',,'b','c',,'d']
copyWithinPreserved[,'a','b',,].copyWithin(2,0) → [,'a',,'a']
entriesElements[...[,'a'].entries()] → [[0,undefined], [1,'a']]
everyIgnored[,'a'].every(x => x==='a') → true
fillFillednew Array(3).fill('a') → ['a','a','a']
filterRemoved['a',,'b'].filter(x => true) → ['a','b']
findElements[,'a'].find(x => true) → undefined
findIndexElements[,'a'].findIndex(x => true) → 0
forEachIgnored[,'a'].forEach((x,i) => log(i)); → 1
indexOfIgnored[,'a'].indexOf(undefined) → -1
joinElements[,'a',undefined,null].join('#') → '#a##'
keysElements[...[,'a'].keys()] → [0,1]
lastIndexOfIgnored[,'a'].lastIndexOf(undefined) → -1
mapPreserved[,'a'].map(x => 1) → [,1]
popElements['a',,].pop() → undefined
pushPreservednew Array(1).push('a') → 2
reduceIgnored['#',,undefined].reduce((x,y)=>x+y) → '#undefined'
reduceRightIgnored['#',,undefined].reduceRight((x,y)=>x+y) → 'undefined#'
reversePreserved['a',,'b'].reverse() → ['b',,'a']
shiftElements[,'a'].shift() → undefined
slicePreserved[,'a'].slice(0,1) → [,]
someIgnored[,'a'].some(x => x !== 'a') → false
sortPreserved[,undefined,'a'].sort() → ['a',undefined,,]
splicePreserved['a',,].splice(1,1) → [,]
toStringElements[,'a',undefined,null].toString() → ',a,,'
unshiftPreserved[,'a'].unshift('b') → 3
valuesElements[...[,'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);


Recommendations

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


  • ECMAScript 5: Chapter “Arrays” in “Speaking JavaScript”

  • ECMAScript 6: Chapter “New Array features” in “Exploring ES6”



Comments

Popular posts from this blog

Steve Lopez and the Importance of Newspapers

Ideas for fixing unconnected computing

Omar to kill me