Movatterモバイル変換


[0]ホーム

URL:


18. New Array features
Table of contents
Please support this book:buy it (PDF, EPUB, MOBI) ordonate
(Ad, please don’t block.)

18.New Array features



18.1Overview

New staticArray methods:

NewArray.prototype methods:

18.2New staticArray methods

The objectArray has new methods.

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

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

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
18.2.1.1Mapping viaArray.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().

18.2.1.2from() in subclasses of Array

Another 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”.

18.2.2Array.of(...items)

Array.of(item_0, item_1, ···) creates an Array whose elements areitem_0,item_1, etc.

18.2.2.1Array.of() as an Array literal for subclasses ofArray

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 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

18.3NewArray.prototype methods

Several new methods are available for Array instances.

18.3.1Iterating over Arrays

The following methods help with iterating over Arrays:

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 ]
18.3.1.1Iterating over[index, element] pairs

You 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);}

18.3.2Searching for Array elements

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)undefined

Array.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)-1

The full signature of the callbackpredicate is:

predicate(element,index,array)
18.3.2.1FindingNaN viafindIndex()

A well-knownlimitation ofArray.prototype.indexOf() is that it can’t findNaN, because it searches for elements via===:

> [NaN].indexOf(NaN)-1

WithfindIndex(), you can useObject.is() (explained inthe chapter on OOP) 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 functionelemIs():

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

18.3.3Array.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]

18.3.4Array.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' ]

18.4ES6 and holes in Arrays

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:

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]undefined

You’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.

18.4.1ECMAScript 6 treats holes likeundefined elements

The 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.

18.4.2Array operations and holes

18.4.2.1Iteration

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.

18.4.2.2Array.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 ]
18.4.2.3Array.prototype methods

In ECMAScript 5, behavior already varied slightly. For example:

ECMAScript 6 adds new kinds of behaviors:

The following table describes howArray.prototype methods handle holes.

MethodHoles are 
concatPreserved['a',,'b'].concat(['c',,'d']) → ['a',,'b','c',,'d']
copyWithinES6Preserved[,'a','b',,].copyWithin(2,0) → [,'a',,'a']
entriesES6Elements[...[,'a'].entries()] → [[0,undefined], [1,'a']]
everyIgnored[,'a'].every(x => x==='a') → true
fillES6Fillednew Array(3).fill('a') → ['a','a','a']
filterRemoved['a',,'b'].filter(x => true) → ['a','b']
findES6Elements[,'a'].find(x => true) → undefined
findIndexES6Elements[,'a'].findIndex(x => true) → 0
forEachIgnored[,'a'].forEach((x,i) => log(i)); → 1
indexOfIgnored[,'a'].indexOf(undefined) → -1
joinElements[,'a',undefined,null].join('#') → '#a##'
keysES6Elements[...[,'a'].keys()] → [0,1]
lastIndexOfIgnored[,'a'].lastIndexOf(undefined) → -1
mapPreserved[,'a'].map(x => 1) → [,1]
popElements['a',,].pop() → undefined
pushPreservednew Array(1).push('a') → 2
reduceIgnored['#',,undefined].reduce((x,y)=>x+y) → '#undefined'
reduceRightIgnored['#',,undefined].reduceRight((x,y)=>x+y) → 'undefined#'
reversePreserved['a',,'b'].reverse() → ['b',,'a']
shiftElements[,'a'].shift() → undefined
slicePreserved[,'a'].slice(0,1) → [,]
someIgnored[,'a'].some(x => x !== 'a') → false
sortPreserved[,undefined,'a'].sort() → ['a',undefined,,]
splicePreserved['a',,].splice(1,1) → [,]
toStringElements[,'a',undefined,null].toString() → ',a,,'
unshiftPreserved[,'a'].unshift('b') → 3
valuesES6Elements[...[,'a'].values()] → [undefined,'a']

Notes:

18.4.3Creating Arrays filled with values

Holes being treated asundefined elements by the new ES6 operations helps with creating Arrays that are filled with values.

18.4.3.1Filling with a fixed value

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.

18.4.3.2Filling with ascending numbers

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 ]
18.4.3.3Filling with computed values

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 ]
18.4.3.4Filling withundefined

If 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 ]

18.4.4Removing holes from Arrays

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' ]

18.5Configuring which objects are spread byconcat() (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.

18.5.1Default for Arrays: spreading

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']

18.5.2Default for non-Arrays: no spreading

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']

18.5.3Detecting Arrays

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)true

18.5.4Symbol.isConcatSpreadable in the standard library

No 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:

Symbol.isConcatSpreadable in the ES6 spec

18.6The numeric range of Array indices

For Arrays, ES6 still hasthe same rules as ES5:

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.

Enforcing the smaller range of normal Arrays

Generic Array methods such aspush() andunshift() allow the larger range of indices. Range checks appropriate for Arrays are performedelsewhere, wheneverlength is set.

Next:19. Maps and Sets

[8]ページ先頭

©2009-2025 Movatter.jp