Array methodsArray.from(arrayLike, mapFunc?, thisArg?)Array.of(...items)Array.prototype methodsArray.prototype.copyWithin()Array.prototype.fill()undefined elementsconcat() (Symbol.isConcatSpreadable)Symbol.isConcatSpreadable in the standard libraryNew staticArray methods:
Array.from(arrayLike, mapFunc?, thisArg?)Array.of(...items)NewArray.prototype methods:
Array.prototype.entries()Array.prototype.keys()Array.prototype.values()Array.prototype.find(predicate, thisArg?)Array.prototype.findIndex(predicate, thisArg?)Array.prototype.copyWithin(target, start, end=this.length)Array.prototype.fill(value, start=0, end=this.length)Array methodsThe objectArray has new methods.
Array.from(arrayLike, mapFunc?, thisArg?)Array.from()’s basic functionality is to convert two kinds of values to Arrays:
length and indexed elements. Examples include the results of DOM operations such asdocument.getElementsByClassName().Map andSet.The following is an example of converting an Array-like object to an Array:
constarrayLike={length:2,0:'a',1:'b'};// for-of only works with iterable valuesfor(constxofarrayLike){// TypeErrorconsole.log(x);}constarr=Array.from(arrayLike);for(constxofarr){// OK, iterableconsole.log(x);}// Output:// a// b
Array.from()Array.from() is also a convenient alternative to usingmap()generically:
constspans=document.querySelectorAll('span.name');// map(), generically:constnames1=Array.prototype.map.call(spans,s=>s.textContent);// Array.from():constnames2=Array.from(spans,s=>s.textContent);
In this example, the result ofdocument.querySelectorAll() is again an Array-like object, not an Array, which is why we couldn’t invokemap() on it. Previously, we converted the Array-like object to an Array in order to callforEach(). Here, we skipped that intermediate step via a generic method call and via the two-parameter version ofArray.from().
from() in subclasses of ArrayAnother use case forArray.from() is to convert an Array-like or iterable value to an instance of a subclass ofArray. For example, if you create a subclassMyArray ofArray and want to convert such an object to an instance ofMyArray, you simply useMyArray.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).
classMyArrayextendsArray{···}constinstanceOfMyArray=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)constinstanceOfMyArray=MyArray.from([1,2,3],x=>x*x);// map(): the result is always an instance of ArrayconstinstanceOfArray=[1,2,3].map(x=>x*x);
The species pattern lets you configure what instances non-static built-in methods (such asslice(),filter() andmap()) return. It is explained in Sect. “The species pattern” in Chap. “Classes”.
Array.of(...items)Array.of(item_0, item_1, ···) creates an Array whose elements areitem_0,item_1, etc.
Array.of() as an Array literal for subclasses ofArrayIf 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 lengthBut how are you supposed to turn values into an instance of a sub-constructor ofArray then? This is whereArray.of() helps (remember that sub-constructors ofArray inherit all ofArray’s methods, includingof()).
classMyArrayextendsArray{···}console.log(MyArray.of(3,11,8)instanceofMyArray);// trueconsole.log(MyArray.of(3).length===1);// true
Array.prototype methodsSeveral new methods are available for Array instances.
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 usingArray.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' ] ]I could also have usedthe spread operator (...) to convert iterators to Arrays:
> [...['a', 'b'].keys()][ 0, 1 ][index, element] pairsYou can combineentries() with ECMAScript 6’sfor-of loop and destructuring to conveniently iterate over[index, element] pairs:
for(const[index,element]of['a','b'].entries()){console.log(index,element);}
Array.prototype.find(predicate, thisArg?)
Returns the first Array element for which the callbackpredicate returnstrue. If there is no such element, it returnsundefined. Example:
> [6, -5, 8].find(x => x < 0)-5> [6, 5, 8].find(x => x < 0)undefinedArray.prototype.findIndex(predicate, thisArg?)
Returns the index of the first element for which the callbackpredicate returnstrue. 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)-1The full signature of the callbackpredicate is:
predicate(element,index,array)
NaN viafindIndex()A well-knownlimitation ofArray.prototype.indexOf() is that it can’t findNaN, because it searches for elements via===:
> [NaN].indexOf(NaN)-1WithfindIndex(), you can useObject.is() (explained inthe chapter on OOP) and will have no such problem:
> [NaN].findIndex(y => Object.is(NaN, y))0You can also adopt a more general approach, by creating a helper functionelemIs():
> function elemIs(x) { return Object.is.bind(Object, x) }> [NaN].findIndex(elemIs(NaN))0Array.prototype.copyWithin()The signature of this method is:
Array.prototype.copyWithin(target:number,start:number,end=this.length):This
It copies the elements whose indices are in the range [start,end) to indextarget and subsequent indices. If the two index ranges overlap, care is taken that all source elements are copied before they are overwritten.
Example:
>constarr=[0,1,2,3];>arr.copyWithin(2,0,2)[0,1,0,1]>arr[0,1,0,1]
Array.prototype.fill()The signature of this method is:
Array.prototype.fill(value:any,start=0,end=this.length):This
It fills an Array with the givenvalue:
> const arr = ['a', 'b', 'c'];> arr.fill(7)[ 7, 7, 7 ]> arr[ 7, 7, 7 ]Optionally, you can restrict where the filling starts and ends:
> ['a', 'b', 'c'].fill(7, 1, 2)[ 'a', 7, 'c' ]Holes are indices “inside” an Array that have no associated element. In other words: An Arrayarr is said to have a hole at indexi if:
i <arr.length!(i in arr)For example: The following Array has a hole at index 1.
> const arr = ['a',,'b']'use strict'> 0 in arrtrue> 1 in arrfalse> 2 in arrtrue> arr[1]undefinedYou’ll see lots of examples involving holes in this section. Should anything ever be unclear, you can consult Sect. “Holes in Arrays” in “Speaking JavaScript” for more information.
ES6 pretends that holes don’t exist (as much as it can while being backward-compatible). And so should you – especially if you consider that holes can also affect performance negatively. Then you don’t have to burden your brain with the numerous and inconsistent rules around holes.
undefined elementsThe general rule for Array methods that are new in ES6 is: each hole is treated as if it were the elementundefined. Examples:
> Array.from(['a',,'b'])[ 'a', undefined, 'b' ]> [,'a'].findIndex(x => x === undefined)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.
The iterator created byArray.prototype[Symbol.iterator] treats each hole as if it were the elementundefined. Take, for example, the following iteratoriter:
> var arr = [, 'a'];> var iter = arr[Symbol.iterator]();If we invokenext() twice, we get the hole at index 0 and the element'a' at index 1. As you can see, the former producesundefined:
> iter.next(){ value: undefined, done: false }> iter.next(){ value: 'a', done: false }Among others, two operations are based onthe iteration protocol. Therefore, these operations also treat holes asundefined elements.
First, the spread operator (...):
> [...[, 'a']][ undefined, 'a' ]Second, thefor-of loop:
for(constxof[,'a']){console.log(x);}// Output:// undefined// a
Note that the Array prototype methods (filter() etc.) do not use the iteration protocol.
Array.from()If its argument is iterable,Array.from() uses iteration to convert it to an Array. Then it works exactly like the spread operator:
> Array.from([, 'a'])[ undefined, 'a' ]ButArray.from() can also convertArray-like objects to Arrays. Then holes becomeundefined, too:
> Array.from({1: 'a', length: 2})[ undefined, 'a' ]With a second argument,Array.from() works mostly likeArray.prototype.map().
However,Array.from() treats holes asundefined:
> Array.from([,'a'], x => x)[ undefined, 'a' ]> Array.from([,'a'], (x,i) => i)[ 0, 1 ]Array.prototype.map() skips them, but preserves them:
> [,'a'].map(x => x)[ , 'a' ]> [,'a'].map((x,i) => i)[ , 1 ]Array.prototype methodsIn 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 howArray.prototype methods handle holes.
| Method | Holes are | |
|---|---|---|
concat | Preserved | ['a',,'b'].concat(['c',,'d']) → ['a',,'b','c',,'d'] |
copyWithinES6 | Preserved | [,'a','b',,].copyWithin(2,0) → [,'a',,'a'] |
entriesES6 | Elements | [...[,'a'].entries()] → [[0,undefined], [1,'a']] |
every | Ignored | [,'a'].every(x => x==='a') → true |
fillES6 | Filled | new Array(3).fill('a') → ['a','a','a'] |
filter | Removed | ['a',,'b'].filter(x => true) → ['a','b'] |
findES6 | Elements | [,'a'].find(x => true) → undefined |
findIndexES6 | 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##' |
keysES6 | 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 |
valuesES6 | Elements | [...[,'a'].values()] → [undefined,'a'] |
Notes:
['a',,].length → 2const log = console.log.bind(console);Holes being treated asundefined elements by the new ES6 operations helps with creating Arrays that are filled with values.
Array.prototype.fill() replaces all Array elements (incl. holes) with a fixed value:
> new Array(3).fill(7)[ 7, 7, 7 ]new Array(3) creates an Array with three holes andfill() replaces each hole with the value7.
Array.prototype.keys() reports keys even if an Array only has holes. It returns an iterable, which you can convert to an Array via the spread operator:
> [...new Array(3).keys()][ 0, 1, 2 ]The mapping function in the second parameter ofArray.from() is notified of holes. Therefore, you can useArray.from() for more sophisticated filling:
> Array.from(new Array(5), (x,i) => i*2)[ 0, 2, 4, 6, 8 ]undefinedIf you need an Array that is filled withundefined, you can use the fact that iteration (as triggered by the spread operator) converts holes toundefineds:
> [...new Array(3)][ undefined, undefined, undefined ]The ES5 methodfilter() lets you remove holes:
> ['a',,'c'].filter(() => true)[ 'a', 'c' ]ES6 iteration (triggered via the spread operator) lets you convert holes toundefined elements:
> [...['a',,'c']][ 'a', undefined, 'c' ]concat() (Symbol.isConcatSpreadable)You can configure howArray.prototype.concat() treats objects by adding an (own or inherited) property whose key is the well-known symbolSymbol.isConcatSpreadable and whose value is a boolean.
By default,Array.prototype.concat()spreads Arrays into its result: their indexed elements become elements of the result:
constarr1=['c','d'];['a','b'].concat(arr1,'e');// ['a', 'b', 'c', 'd', 'e']
WithSymbol.isConcatSpreadable, you can override the default and avoid spreading for Arrays:
constarr2=['c','d'];arr2[Symbol.isConcatSpreadable]=false;['a','b'].concat(arr2,'e');// ['a', 'b', ['c','d'], 'e']
For non-Arrays, the default is not to spread:
constarrayLike={length:2,0:'c',1:'d'};console.log(['a','b'].concat(arrayLike,'e'));// ['a', 'b', arrayLike, 'e']console.log(Array.prototype.concat.call(arrayLike,['e','f'],'g'));// [arrayLike, 'e', 'f', 'g']
You can useSymbol.isConcatSpreadable to force spreading:
arrayLike[Symbol.isConcatSpreadable]=true;console.log(['a','b'].concat(arrayLike,'e'));// ['a', 'b', 'c', 'd', 'e']console.log(Array.prototype.concat.call(arrayLike,['e','f'],'g'));// ['c', 'd', 'e', 'f', 'g']
How doesconcat() determine if a parameter is an Array? It uses the same algorithm asArray.isArray(). Whether or notArray.prototype is in the prototype chain makes no difference for that algorithm. That is important, because, in ES5 and earlier, hacks were used to subclassArray and those must continue to work (see the section on__proto__ in this book):
> const arr = [];> Array.isArray(arr)true> Object.setPrototypeOf(arr, null);> Array.isArray(arr)trueSymbol.isConcatSpreadable in the standard libraryNo object in the ES6 standard library has a property with the keySymbol.isConcatSpreadable. This mechanism therefore exists purely for browser APIs and user code.
Consequences:
Array are spread by default (because their instances are Array objects).Array can prevent its instances from being spread by setting a property tofalse whose key isSymbol.isConcatSpreadable. That property can be a prototype property or an instance property.concat() if property[Symbol.isConcatSpreadable] istrue. That would enable one, for example, to turn on spreading for some Array-like DOM collections.concat(), either.Symbol.isConcatSpreadable in the ES6 specArray.prototype.concat(), you can see that spreading requires an object to be Array-like (propertylength plus indexed elements).IsConcatSpreadable(). The last step is the default (equivalent toArray.isArray()) and the property[Symbol.isConcatSpreadable] is retrieved via a normalGet() operation, meaning that it doesn’t matter whether it is own or inherited.For Arrays, ES6 still hasthe same rules as ES5:
l are in the range 0 ≤l ≤ 232−1.i are in the range 0 ≤i < 232−1.Strings and Typed Arrays have a larger range of indices: 0 ≤i < 253−1. The upper bound of that range is due to 253−1 being the largest integer that JavaScript’s floating point numbers can represent safely. For details, see Sect. “Safe integers”.
The only reason for the smaller index range of normal Arrays is backward compatibility.
Generic Array methods such aspush() andunshift() allow the larger range of indices. Range checks appropriate for Arrays are performedelsewhere, wheneverlength is set.