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()
andsome()
ignore holes.map()
skips but preserves holes.join()
andtoString()
treat holes as if they wereundefined
elements, but interprets bothnull
andundefined
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 elementundefined
.find()
andfindIndex()
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);
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
Post a Comment