2013-07-07

Array iteration and holes in JavaScript

The blog post describes how various functions and methods that deal with arrays are affected by holes [1].

Running example

All of the code below assumes that the following variable declaration has been made:
    var arr = [ 'a',, 'b' ];
Above, we have created an array that has the element 'a' at index 0, a hole (no element at all) at index 1 and the element 'b' at index 2.

Preliminaries

Trailing holes

For this blog post, it is important to keep in mind that JavaScript ignores trailing commas in arrays. That means that you need to write two commas to create a trailing hole:
    > [ 'a', 'b', ]
    [ 'a', 'b' ]
    > [ 'a', 'b',, ]
    [ 'a', 'b', ,  ]

Copying arrays

We use slice() to create a copy arr2 of an existing array arr:
    var arr2 = arr.slice();
The right-hand side of the above assignment is equivalent to:
    arr.slice(0, arr.length)

Array methods

forEach() skips holes:
    > arr.forEach(function (x,i) { console.log(i+'.'+x) })
    0.a
    2.b
every() also skips holes (similarly: some()):
    > arr.every(function (x) { return x.length === 1 })
    true
map() skips, but preserves holes:
    > arr.map(function (x,i) { return i+'.'+x })
    [ '0.a', , '2.b' ]
filter() eliminates holes:
    > arr.filter(function (x) { return true })
    [ 'a', 'b' ]
join() converts holes and undefineds to empty strings.
    > arr.join('-')
    'a--b'
    > [ 'a', undefined, 'b' ].join('-')
    'a--b'

Other array methods

All other array methods preserve holes. For example, sort():
    > var arr2 = arr.slice()
    > arr2.sort()
    [ 'a', 'b', ,  ]

Loops

The for loop does not access arrays, the for-in loop correctly lists property keys:
    > var arr2 = arr.slice()
    > arr2.foo = 123;
    > for(var key in arr2) { console.log(key) }
    0
    2
    foo

Function.prototype.apply()

apply() treats holes as undefined elements. That allows you to easily create an array with a given number of undefineds [1]:
    > Array.apply(null, Array(3))
    [ undefined, undefined, undefined ]
apply() nicely plugs holes of empty arrays. You cannot, however use it to do so for arbitrary arrays, which may or may not contain holes. For example, the arbitrary array [2] does not contain holes, so apply() should return it “unchanged”. But it doesn’t, it creates an empty array whose length is 2 (because the function Array() interprets single numbers as array lengths, not as array elements).
    > Array.apply(null, [2])
    [ , ,]

Conclusion and recommendation

We have seen that JavaScript handles holes in a variety of ways. Thankfully, you normally don’t need to know how holes are handled, because arrays with holes should be avoided. Holes affect performance negatively and are rarely useful.

Further reading

  1. JavaScript: sparse arrays vs. dense arrays
    What are holes and how do they differ from elements that are undefined?
  2. Iterating over arrays and objects in JavaScript
    General information on iterating over arrays.

No comments: