18. 新しい配列の機能
目次
この本を応援してください: 購入する (PDF, EPUB, MOBI) または 寄付する
(広告です。ブロックしないでください。)

18. 新しい配列の機能



18.1 概要

新しい静的Arrayメソッド

新しいArray.prototypeメソッド

18.2 新しい静的Arrayメソッド

オブジェクトArrayには新しいメソッドがあります。

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

Array.from()の基本的な機能は、2種類の値を配列に変換することです。

以下は、配列のようなオブジェクトを配列に変換する例です。

const arrayLike = { length: 2, 0: 'a', 1: 'b' };

// for-of only works with iterable values
for (const x of arrayLike) { // TypeError
    console.log(x);
}

const arr = Array.from(arrayLike);
for (const x of arr) { // OK, iterable
    console.log(x);
}
// Output:
// a
// b
18.2.1.1 Array.from()によるマッピング

Array.from()は、map()ジェネリックに使用する便利な代替手段でもあります。

const spans = document.querySelectorAll('span.name');

// map(), generically:
const names1 = Array.prototype.map.call(spans, s => s.textContent);

// Array.from():
const names2 = Array.from(spans, s => s.textContent);

この例では、document.querySelectorAll()の結果は、再び配列ではなく配列のようなオブジェクトであるため、map()を呼び出すことができませんでした。以前は、forEach()を呼び出すために、配列のようなオブジェクトを配列に変換していました。ここでは、ジェネリックメソッド呼び出しとArray.from()の2パラメータバージョンを使用して、その中間ステップをスキップしました。

18.2.1.2 Arrayのサブクラスのfrom()

Array.from()のもう1つのユースケースは、配列のような値または反復可能な値をArrayのサブクラスのインスタンスに変換することです。たとえば、ArrayのサブクラスMyArrayを作成し、そのようなオブジェクトをMyArrayのインスタンスに変換する場合は、MyArray.from()を使用するだけです。それが機能する理由は、コンストラクターがECMAScript 6で互いに継承する(スーパコンストラクターはサブコンストラクターのプロトタイプである)ためです。

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

この機能をマッピングと組み合わせて、結果のコンストラクターを制御するマップ操作を取得することもできます。

// from() – determine the result’s constructor via the receiver
// (in this case, MyArray)
const instanceOfMyArray = MyArray.from([1, 2, 3], x => x * x);

// map(): the result is always an instance of Array
const instanceOfArray   = [1, 2, 3].map(x => x * x);

種パターンを使用すると、非静的組み込みメソッド(slice()filter()map()など)が返すインスタンスを構成できます。これは、「クラス」の章の「種パターン」で説明されています。

18.2.2 Array.of(...items)

Array.of(item_0, item_1, ···)は、要素がitem_0item_1などである配列を作成します。

18.2.2.1 Array.of()は、Arrayのサブクラスの配列リテラルとして

いくつかの値を配列に変換する場合は、特に単一の値が数値である場合に配列コンストラクターが正しく機能しないため、常に配列リテラルを使用する必要があります(この癖の詳細情報

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

では、どのように値をArrayのサブコンストラクターのインスタンスに変換する必要がありますか?これは、Array.of()が役立つ場所です(Arrayのサブコンストラクターは、of()を含むArrayのすべてのメソッドを継承することを覚えておいてください)。

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

18.3 新しいArray.prototypeメソッド

配列インスタンスでいくつかの新しいメソッドが利用可能です。

18.3.1 配列の反復処理

次のメソッドは、配列の反復処理に役立ちます。

前述の各メソッドの結果は値のシーケンスですが、配列として返されるのではなく、イテレーターを介して1つずつ明らかにされます。例を見てみましょう。イテレーターの内容を配列に入れるために、Array.from()を使用しています。

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

また、スプレッド演算子(...を使用して、イテレーターを配列に変換することもできました。

> [...['a', 'b'].keys()]
[ 0, 1 ]
18.3.1.1 [index, element]ペアの反復処理

entries()をECMAScript 6のfor-ofループおよびデストラクチャリングと組み合わせて、[index, element]ペアを便利に反復処理できます。

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

18.3.2 配列要素の検索

Array.prototype.find(predicate, thisArg?)
コールバックpredicatetrueを返す最初の配列要素を返します。そのような要素がない場合は、undefinedを返します。例

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

Array.prototype.findIndex(predicate, thisArg?)
コールバックpredicatetrueを返す最初の要素のインデックスを返します。そのような要素がない場合は、-1を返します。例

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

コールバックpredicateの完全な署名は次のとおりです。

predicate(element, index, array)
18.3.2.1 findIndex()によるNaNの検索

Array.prototype.indexOf()のよく知られた制限は、===を介して要素を検索するため、NaNを見つけることができないことです。

> [NaN].indexOf(NaN)
-1

findIndex()を使用すると、Object.is()OOPの章で説明)を使用でき、そのような問題は発生しません。

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

ヘルパー関数elemIs()を作成することで、より一般的なアプローチを採用することもできます。

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

18.3.3 Array.prototype.copyWithin()

このメソッドの署名は次のとおりです。

Array.prototype.copyWithin(target : number,
    start : number, end = this.length) : This

インデックスが範囲[start,end)にある要素をインデックスtargetとその後のインデックスにコピーします。2つのインデックス範囲が重なる場合、すべてのソース要素が上書きされる前にコピーされるように注意が払われます。

> const arr = [0,1,2,3];
> arr.copyWithin(2, 0, 2)
[ 0, 1, 0, 1 ]
> arr
[ 0, 1, 0, 1 ]

18.3.4 Array.prototype.fill()

このメソッドの署名は次のとおりです。

Array.prototype.fill(value : any, start=0, end=this.length) : This

配列を指定されたvalueで埋めます。

> const arr = ['a', 'b', 'c'];
> arr.fill(7)
[ 7, 7, 7 ]
> arr
[ 7, 7, 7 ]

オプションで、埋め込みが開始および終了する場所を制限できます。

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

18.4 ES6と配列の穴

穴は、関連付けられた要素がない配列「内」のインデックスです。言い換えれば、配列arrは、次の場合にインデックスiに穴があるとされます。

例:次の配列には、インデックス1に穴があります。

> const arr = ['a',,'b']
'use strict'
> 0 in arr
true
> 1 in arr
false
> 2 in arr
true
> arr[1]
undefined

このセクションでは、穴に関する多くの例が表示されます。不明な点がある場合は、「JavaScriptの理解」のセクション「配列の穴」を参照してください。

18.4.1 ECMAScript 6は穴をundefined要素のように扱う

ES6で新しい配列メソッドの一般的なルールは、各穴は要素undefinedであるかのように扱われることです。例

> Array.from(['a',,'b'])
[ 'a', undefined, 'b' ]
> [,'a'].findIndex(x => x === undefined)
0
> [...[,'a'].entries()]
[ [ 0, undefined ], [ 1, 'a' ] ]

これは、人々を穴から遠ざけ、長期的に単純化することを目的としています。残念ながら、それは物事がさらに一貫性がなくなったことを意味します。

18.4.2 配列操作と穴

18.4.2.1 反復処理

Array.prototype[Symbol.iterator]によって作成されたイテレーターは、各穴を要素undefinedであるかのように扱います。たとえば、次のイテレーターiterを使用します。

> var arr = [, 'a'];
> var iter = arr[Symbol.iterator]();

next()を2回呼び出すと、インデックス0に穴があり、インデックス1に要素'a'があります。ご覧のとおり、前者はundefinedを生成します。

> iter.next()
{ value: undefined, done: false }
> iter.next()
{ value: 'a', done: false }

とりわけ、2つの操作が反復プロトコルに基づいています。したがって、これらの操作も穴をundefined要素として扱います。

まず、スプレッド演算子(...)です。

> [...[, 'a']]
[ undefined, 'a' ]

次に、for-ofループです。

for (const x of [, 'a']) {
  console.log(x);
}
// Output:
// undefined
// a

配列プロトタイプメソッド(filter()など)は、反復プロトコルを使用しないことに注意してください。

18.4.2.2 Array.from()

その引数が反復可能な場合、Array.from()は反復処理を使用してそれを配列に変換します。次に、スプレッド演算子とまったく同じように機能します。

> Array.from([, 'a'])
[ undefined, 'a' ]

しかし、Array.from()配列のようなオブジェクトを配列に変換することもできます。この場合、穴はundefinedになります。

> Array.from({1: 'a', length: 2})
[ undefined, 'a' ]

2番目の引数を指定すると、Array.from()はほとんどArray.prototype.map()のように動作します。

ただし、Array.from()は穴をundefinedとして扱います。

> Array.from([,'a'], x => x)
[ undefined, 'a' ]
> Array.from([,'a'], (x,i) => i)
[ 0, 1 ]

Array.prototype.map()は穴をスキップしますが、保持します。

> [,'a'].map(x => x)
[ , 'a' ]
> [,'a'].map((x,i) => i)
[ , 1 ]
18.4.2.3 Array.prototype メソッド

ECMAScript 5では、すでに動作に若干の違いがありました。例えば

ECMAScript 6では、新しい種類の動作が追加されました。

次の表は、Array.prototypeメソッドが穴をどのように処理するかを示しています。

メソッド 穴は  
concat 保持される ['a',,'b'].concat(['c',,'d']) → ['a',,'b','c',,'d']
copyWithinES6 保持される [,'a','b',,].copyWithin(2,0) → [,'a',,'a']
entriesES6 要素 [...[,'a'].entries()] → [[0,undefined], [1,'a']]
every 無視される [,'a'].every(x => x==='a') → true
fillES6 埋められる new Array(3).fill('a') → ['a','a','a']
filter 削除される ['a',,'b'].filter(x => true) → ['a','b']
findES6 要素 [,'a'].find(x => true) → undefined
findIndexES6 要素 [,'a'].findIndex(x => true) → 0
forEach 無視される [,'a'].forEach((x,i) => log(i)); → 1
indexOf 無視される [,'a'].indexOf(undefined) → -1
join 要素 [,'a',undefined,null].join('#') → '#a##'
keysES6 要素 [...[,'a'].keys()] → [0,1]
lastIndexOf 無視される [,'a'].lastIndexOf(undefined) → -1
map 保持される [,'a'].map(x => 1) → [,1]
pop 要素 ['a',,].pop() → undefined
push 保持される new Array(1).push('a') → 2
reduce 無視される ['#',,undefined].reduce((x,y)=>x+y) → '#undefined'
reduceRight 無視される ['#',,undefined].reduceRight((x,y)=>x+y) → 'undefined#'
reverse 保持される ['a',,'b'].reverse() → ['b',,'a']
shift 要素 [,'a'].shift() → undefined
slice 保持される [,'a'].slice(0,1) → [,]
some 無視される [,'a'].some(x => x !== 'a') → false
sort 保持される [,undefined,'a'].sort() → ['a',undefined,,]
splice 保持される ['a',,].splice(1,1) → [,]
toString 要素 [,'a',undefined,null].toString() → ',a,,'
unshift 保持される [,'a'].unshift('b') → 3
valuesES6 要素 [...[,'a'].values()] → [undefined,'a']

18.4.3 値で埋められた配列の作成

新しいES6操作で穴がundefined要素として扱われることは、値で埋められた配列の作成に役立ちます。

18.4.3.1 固定値で埋める

Array.prototype.fill()は、すべての配列要素(穴を含む)を固定値で置き換えます。

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

new Array(3)は3つの穴を持つ配列を作成し、fill()は各穴を値7で置き換えます。

18.4.3.2 昇順の数値で埋める

Array.prototype.keys()は、配列に穴しかない場合でもキーを報告します。これはイテラブルを返し、スプレッド演算子を使って配列に変換できます。

> [...new Array(3).keys()]
[ 0, 1, 2 ]
18.4.3.3 計算された値で埋める

Array.from()の2番目のパラメータのマップ関数は、穴を通知されます。したがって、Array.from()を使用して、より洗練された埋め込みを行うことができます。

> Array.from(new Array(5), (x,i) => i*2)
[ 0, 2, 4, 6, 8 ]
18.4.3.4 undefinedで埋める

undefinedで埋められた配列が必要な場合は、イテレーション(スプレッド演算子によってトリガーされる)が穴をundefinedに変換するという事実を利用できます。

> [...new Array(3)]
[ undefined, undefined, undefined ]

18.4.4 配列から穴を削除する

ES5メソッドfilter()を使用すると、穴を削除できます。

> ['a',,'c'].filter(() => true)
[ 'a', 'c' ]

ES6イテレーション(スプレッド演算子によってトリガーされる)を使用すると、穴をundefined要素に変換できます。

> [...['a',,'c']]
[ 'a', undefined, 'c' ]

18.5 concat()でスプレッドされるオブジェクトを設定する (Symbol.isConcatSpreadable)

キーがよく知られたシンボルSymbol.isConcatSpreadableで、値がブール値である(独自または継承された)プロパティを追加することで、Array.prototype.concat()がオブジェクトをどのように扱うかを設定できます。

18.5.1 配列のデフォルト: スプレッド

デフォルトでは、Array.prototype.concat()は配列を結果にスプレッドします。つまり、インデックス付き要素が結果の要素になります。

const arr1 = ['c', 'd'];
['a', 'b'].concat(arr1, 'e');
    // ['a', 'b', 'c', 'd', 'e']

Symbol.isConcatSpreadableを使用すると、デフォルトをオーバーライドし、配列のスプレッドを回避できます。

const arr2 = ['c', 'd'];
arr2[Symbol.isConcatSpreadable] = false;
['a', 'b'].concat(arr2, 'e');
    // ['a', 'b', ['c','d'], 'e']

18.5.2 配列以外のデフォルト: スプレッドしない

配列以外の場合、デフォルトはスプレッドしないことです。

const arrayLike = {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']

Symbol.isConcatSpreadableを使用して、スプレッドを強制できます。

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.3 配列の検出

concat()は、パラメータが配列かどうかをどのように判断するのでしょうか? これは、Array.isArray()と同じアルゴリズムを使用します。Array.prototypeがプロトタイプチェーンにあるかどうかは、そのアルゴリズムにとっては何の違いもありません。これは重要です。なぜなら、ES5以前では、Arrayをサブクラス化するためにハックが使用され、それらは引き続き機能する必要があるからです (この本の__proto__に関するセクションを参照)。

> const arr = [];
> Array.isArray(arr)
true

> Object.setPrototypeOf(arr, null);
> Array.isArray(arr)
true

18.5.4 標準ライブラリでのSymbol.isConcatSpreadable

ES6標準ライブラリには、キーがSymbol.isConcatSpreadableであるプロパティを持つオブジェクトはありません。したがって、このメカニズムは、ブラウザAPIとユーザーコードのみを対象としています。

結果

18.6 配列インデックスの数値範囲

配列の場合、ES6はES5と同じルールを持っています。

文字列とTyped Arraysは、より大きなインデックス範囲を持っています。0 ≤ i < 253−1。その範囲の上限は、253−1がJavaScriptの浮動小数点数が安全に表現できる最大の整数であるためです。詳細については、「安全な整数」を参照してください。

通常の配列のインデックス範囲が小さい唯一の理由は、下位互換性です。

次へ: 19. マップとセット