ECMAScript 6’s new array methods
Check out my book (free online): “Exploring ES6”. Updated version of this blog post: chapter “New Array features”.
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 asdocument.getElementsByClassName()
. - Iterable objects, whose contents can be retrieved one element at a time. Arrays are iterable, as are ECMAScript’s new data structures
Map
andSet
.
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.
Mapping via Array.from()
Array.from()
is also a convenient alternative to using map()
generically:
let spans = document.querySelectorAll('span.name');
// map(), generically:
let names1 = Array.prototype.map.call(spans, s => s.textContent);
// Array.from():
let names2 = Array.from(spans, s => s.textContent);
The second parameter of both methods is an arrow function.
In this example, the result of document.querySelectorAll()
is again an array-like object, not an array, which is why we couldn’t invoke map()
on it. Previously, we converted the array-like object to an array in order to call forEach()
. Here, we skipped that intermediate step via a generic method call and via the two-parameter version of Array.from()
.
Holes
Array.from()
ignores holes [3] in arrays, it treats them as if they were undefined
elements:
> Array.from([0,,2])
[ 0, undefined, 2 ]
That means that you can use Array.from()
to create and fill an array:
> Array.from(new Array(5), () => 'a')
[ 'a', 'a', 'a', 'a', 'a' ]
> Array.from(new Array(5), (x,i) => i)
[ 0, 1, 2, 3, 4 ]
If you want to fill an array with a fixed value (first one of the previous two examples) then Array.prototype.fill()
(see below) is a better choice.
from() in subclasses of Array
Another use case for Array.from()
is to convert an array-like or iterable object to an instance of a subclass of Array
. For example, if you create a subclass MyArray
of Array
(subclassing arrays is explained in [1]) and want to convert such an object to an instance of MyArray
, 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 its sub-constructors).
class MyArray extends Array {
...
}
let instanceOfMyArray = MyArray.from(anIterable);
You can also combine this functionality with mapping, to get a map operation where you control the result’s constructor:
// from() – determine the result’s constructor via the receiver
// (in this case, MyArray)
let instanceOfMyArray = MyArray.from([1, 2, 3], x => x * x);
// map(): the result is always an instance of Array
let instanceOfArray = [1, 2, 3].map(x => x * x);
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?
- Some of them are already available in browsers. As usual, check kangax’s ECMAScript 6 compatibility table.
- Paul Miller’s es6-shim library has backported them to ECMAScript 5.
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
Comments
Post a Comment