2014-05-08

ECMAScript 6’s new array methods

This blog post explains what new array methods ECMAScript 6 will bring and how to use them in current browsers.

Note: I’m using the terms constructor and class interchangeably.

Class methods

Array has gained methods of its own.

Array.from(arrayLike, mapFunc?, thisArg?)

Array.from()’s basic functionality is to convert two kinds of objects to arrays:

  • Array-like objects, which have a property length and indexed elements. Examples include the results of DOM operations such as document.getElementsByClassName().
  • Iterable objects, whose contents can be retrieved one element at a time. Arrays are iterable, as are ECMAScript’s new data structures Map and Set.

The following code is an example of converting an array-like object to an array:

    let lis = document.querySelectorAll('ul.fancy li');
    Array.from(lis).forEach(function (li) {
        console.log(node);
    });

The result of querySelectorAll() is not an array and does not have a forEach() method, which is why we need to convert it to an array before we can use that method.

Array.from allows you to control the constructor it uses for its result. For example, if you create a subclass MyArray of Array (subclassing arrays is explained in [1]) and want to convert something array-like or iterable to an instance of it, you simply use MyArray.from(). The reason that that works is because constructors inherit from each other in ECMAScript 6 (a super-constructor is the prototype of a sub-constructor).

    class MyArray extends Array {
        ...
    }
    let instanceOfMyArray = MyArray.from(anIterable);

Now we can see why the parameter mapFunc is useful: it makes Array.from() a variant of Array.prototype.map() that produces a result whose constructor you can specify (via the constructor on which from() is invoked):

    let instanceOfMyArray = MyArray.from([1, 2, 3], x => x * x);
    let instanceOfArray   = [1, 2, 3].map(x => x * x);

The (last) parameter of both methods is an arrow function.

Array.of(...items)

If you want to turn several values into an array, you should always use an array literal, especially since the array constructor doesn’t work properly if there is a single value that is a number (more information on this quirk):

    > new Array(3, 11, 8)
    [ 3, 11, 8 ]
    > new Array(3)
    [ , ,  ,]
    > new Array(3.1)
    RangeError: Invalid array length

But how are you supposed to turn values into an instance of a sub-constructor of Array then? This is where Array.of() helps (remember that sub-constructors of Array inherit all of Array’s methods, including of()).

    class MyArray extends Array {
        ...
    }
    console.log(MyArray.of(3, 11, 8) instanceof MyArray); // true
    console.log(MyArray.of(3).length === 1); // true

Array.of() is also handy as a function that doesn’t have Array()’s quirk related to wrapping values in arrays. However, be careful about an Array.prototype.map() pecularity that can trip you up here:

    > ['a', 'b'].map(Array.of)
    [ [ 'a', 0, [ 'a', 'b' ] ],
      [ 'b', 1, [ 'a', 'b' ] ] ]
    > ['a', 'b'].map(x => Array.of(x)) // better
    [ [ 'a' ], [ 'b' ] ]
    > ['a', 'b'].map(x => [x]) // best (in this case)
    [ [ 'a' ], [ 'b' ] ]

As you can see above, map() passes three parameters to its callback, the last two are simply often ignored (details).

Prototype methods

Several new methods are available for array instances.

Iterating over arrays

The following methods help with iterating over arrays:

  • Array.prototype.entries()
  • Array.prototype.keys()
  • Array.prototype.values()

The result of each of the aforementioned methods is a sequence of values, but they are not returned as an array; they are revealed one by one, via an iterator. Let’s look at an example (I’m using Array.from() to put the iterators’ contents into arrays):

    > Array.from([ 'a', 'b' ].keys())
    [ 0, 1 ]
    > Array.from([ 'a', 'b' ].values())
    [ 'a', 'b' ]
    > Array.from([ 'a', 'b' ].entries())
    [ [ 0, 'a' ],
      [ 1, 'b' ] ]

You can combine entries() with ECMAScript 6’s for-of loop [2] and destructuring to conveniently iterate over (index, element) pairs:

    for (let [index, elem] of ['a', 'b'].entries()) {
        console.log(index, elem);
    }

Note: this code already works in the current Firefox.

Searching for array elements

Array.prototype.find(predicate, thisArg?)
returns the first array element for which the callback predicate returns true. If there is no such element, it returns undefined. Example:

    > [6, -5, 8].find(x => x < 0)
    -5
    > [6, 5, 8].find(x => x < 0)
    undefined

Array.prototype.findIndex(predicate, thisArg?)
returns the index of the first element for which the callback predicate returns true. If there is no such element, it returns -1. Example:

    > [6, -5, 8].findIndex(x => x < 0)
    1
    > [6, 5, 8].findIndex(x => x < 0)
    -1

Both find* methods ignore holes [3]. The full signature of the callback predicate is:

    predicate(element, index, array)
Finding NaN via findIndex()

A well-known limitation of Array.prototype.indexOf() is that it can’t find NaN, because it searches for elements via ===:

    > [NaN].indexOf(NaN)
    -1

With findIndex(), you can use Object.is() [4] and will have no such problem:

    > [NaN].findIndex(y => Object.is(NaN, y))
    0

You can also adopt a more general approach, by creating a helper function elemIs():

    > function elemIs(x) { return Object.is.bind(Object, x) }
    > [NaN].findIndex(elemIs(NaN))
    0

Array.prototype.fill(value, start?, end?)

Fills an array with the given value:

    > ['a', 'b', 'c'].fill(7)
    [ 7, 7, 7 ]

Holes [3] get no special treatment:

    > new Array(3).fill(7)
    [ 7, 7, 7 ]

Optionally, you can restrict where the filling starts and ends:

    > ['a', 'b', 'c'].fill(7, 1, 2)
    [ 'a', 7, 'c' ]

When can I use the new array methods?

References

  1. Subclassing builtins in ECMAScript 6
  2. Iterators and generators in ECMAScript 6
  3. Holes in Arrays” (Speaking JavaScript)
  4. Stricter equality in JavaScript

6 comments:

Axel Rauschmayer said...

Good overview!

1. Instead of "constructor methods", how about the term "static Array functions"?

2. In section 1.2., could you define what Array.of() does? That section is a bit confusing without the definition.

Axel Rauschmayer said...

Correction: "Static Array *methods*" is best, I think.

Axel Rauschmayer said...

s/es5-shim/es6-shim/

Axel Rauschmayer said...

Indeed. Fixed, thanks!

Axel Rauschmayer said...

My current favorite is “class methods”.

Axel Rauschmayer said...

Thanks for this nice article.
Exciting ES6 news! =]