第18章 配列
目次
書籍を購入する
(広告です。ブロックしないでください。)

第18章 配列

配列とは、インデックス(ゼロから始まる自然数)から任意の値へのマッピングです。値(マップの範囲)は配列の要素と呼ばれます。配列を作成する最も便利な方法は、配列リテラルを使うことです。そのようなリテラルは、配列要素を列挙します。要素の位置は、暗黙的にそのインデックスを指定します。

この章では、まず、インデックスアクセスやlengthプロパティなどの基本的な配列の仕組みを説明し、その後、配列メソッドについて説明します。

概要

このセクションでは、配列の簡単な概要を説明します。詳細については後で説明します。

最初の例として、配列リテラルを使用して配列arrを作成し(「配列の作成」を参照)、要素にアクセスします(「配列のインデックス」を参照)。

> var arr = [ 'a', 'b', 'c' ]; // array literal
> arr[0]  // get element 0
'a'
> arr[0] = 'x';  // set element 0
> arr
[ 'x', 'b', 'c' ]

配列のプロパティであるlengthを使用して(「length」を参照)、要素を削除したり追加したりできます。

> var arr = [ 'a', 'b', 'c' ];
> arr.length
3
> arr.length = 2;  // remove an element
> arr
[ 'a', 'b' ]
> arr[arr.length] = 'd';  // append an element
> arr
[ 'a', 'b', 'd' ]

配列メソッドpush()は、要素を追加する別の方法を提供します。

> var arr = [ 'a', 'b' ];
> arr.push('d')
3
> arr
[ 'a', 'b', 'd' ]

配列はタプルではなくマップである

ECMAScript標準では、配列はインデックスから値へのマップ(辞書)として規定されています。言い換えれば、配列は連続的でなくてもよく、穴があっても構いません。例:

> var arr = [];
> arr[0] = 'a';
'a'
> arr[2] = 'b';
'b'
> arr
[ 'a', , 'b' ]

上記の配列には穴があります。インデックス1には要素がありません。「配列の穴」で穴について詳しく説明します。

ほとんどのJavaScriptエンジンは、内部的に穴のない配列を最適化し、連続的に格納することに注意してください。

配列はプロパティを持つこともできる

配列は依然としてオブジェクトであり、オブジェクトのプロパティを持つことができます。それらは実際の配列の一部とは見なされません。つまり、配列要素とは見なされません。

> var arr = [ 'a', 'b' ];
> arr.foo = 123;
> arr
[ 'a', 'b' ]
> arr.foo
123

配列の作成

配列は配列リテラルで作成します。

var myArray = [ 'a', 'b', 'c' ];

配列の末尾のカンマは無視されます。

> [ 'a', 'b' ].length
2
> [ 'a', 'b', ].length
2
> [ 'a', 'b', ,].length  // hole + trailing comma
3

Arrayコンストラクター

コンストラクターArrayを使用するには、2つの方法があります。指定された長さの空の配列を作成するか、要素が指定された値である配列を作成できます。このコンストラクターでは、newはオプションです。(newなしで)通常の関数として呼び出すことは、コンストラクターとして呼び出すことと同じです。

指定された長さの空の配列を作成する

指定された長さの空の配列には、穴しかありません!したがって、このバージョンのコンストラクターを使用することはほとんど意味がありません。

> var arr = new Array(2);
> arr.length
2
> arr  // two holes plus trailing comma (ignored!)
[ , ,]

一部のエンジンでは、この方法でArray()を呼び出すと、連続したメモリが事前に割り当てられ、パフォーマンスがわずかに向上する場合があります。ただし、冗長性が増すだけの価値があることを確認してください!

要素で配列を初期化する(避けるべき!)

このArrayの呼び出し方法は、配列リテラルに似ています。

// The same as ['a', 'b', 'c']:
var arr1 = new Array('a', 'b', 'c');

問題は、単一の数値を持つ配列を作成できないことです。なぜなら、それはlengthがその数値である配列を作成すると解釈されるためです。

> new Array(2)  // alas, not [ 2 ]
[ , ,]

> new Array(5.7)  // alas, not [ 5.7 ]
RangeError: Invalid array length

> new Array('abc')  // ok
[ 'abc' ]

多次元配列

要素に複数の次元が必要な場合は、配列をネストする必要があります。このようなネストされた配列を作成すると、最も内側の配列は必要に応じて成長できます。ただし、要素に直接アクセスする場合は、少なくとも外側の配列を作成する必要があります。次の例では、三目並べ用の3x3の行列を作成します。行列は(必要に応じて行を拡張するのではなく)データで完全に埋められています。

// Create the Tic-tac-toe board
var rows = [];
for (var rowCount=0; rowCount < 3; rowCount++) {
    rows[rowCount] = [];
    for (var colCount=0; colCount < 3; colCount++) {
        rows[rowCount][colCount] = '.';
    }
}

// Set an X in the upper right corner
rows[0][2] = 'X';  // [row][column]

// Print the board
rows.forEach(function (row) {
    console.log(row.join(' '));
});

出力は次のとおりです。

. . X
. . .
. . .

この例では、一般的なケースを示したかったのです。明らかに、行列が非常に小さく、固定された次元を持つ場合は、配列リテラルで設定できます。

var rows = [ ['.','.','.'], ['.','.','.'], ['.','.','.'] ];

配列のインデックス

配列のインデックスを扱うときは、次の制限事項に注意する必要があります。

  • インデックスは、範囲0 ≤ i < 232−1の数値iです。
  • 最大長は232−1です。

範囲外のインデックスは、通常のプロパティキー(文字列!)として扱われます。それらは配列要素として表示されず、プロパティlengthにも影響を与えません。例:

> var arr = [];

> arr[-1] = 'a';
> arr
[]
> arr['-1']
'a'

> arr[4294967296] = 'b';
> arr
[]
> arr['4294967296']
'b'

in演算子とインデックス

in演算子は、オブジェクトが特定のキーを持つプロパティを持っているかどうかを検出します。ただし、配列に特定の要素インデックスが存在するかどうかを判断するためにも使用できます。例:

> var arr = [ 'a', , 'b' ];
> 0 in arr
true
> 1 in arr
false
> 10 in arr
false

配列要素の削除

プロパティの削除に加えて、delete演算子は配列要素も削除します。要素を削除すると穴が作成されます(lengthプロパティは更新されません)。

> var arr = [ 'a', 'b' ];
> arr.length
2
> delete arr[1]  // does not update length
true
> arr
[ 'a',  ]
> arr.length
2

配列の長さを短くして、末尾の配列要素を削除することもできます(詳細については「length」を参照)。穴を作成せずに要素を削除するには(つまり、後続の要素のインデックスがデクリメントされる)、Array.prototype.splice()を使用します(「要素の追加と削除(破壊的)」を参照)。この例では、インデックス1で2つの要素を削除します。

> var arr = ['a', 'b', 'c', 'd'];
> arr.splice(1, 2) // returns what has been removed
[ 'b', 'c' ]
> arr
[ 'a', 'd' ]

配列のインデックスの詳細

ヒント

これは高度なセクションです。通常、ここで説明する詳細を知る必要はありません。

配列のインデックスは見た目どおりではありません。今まで、配列のインデックスは数値であると仮定していました。そして、それがJavaScriptエンジンが内部的に配列を実装する方法です。ただし、ECMAScript仕様では、インデックスは異なるものとして捉えられています。セクション15.4を言い換えると、

  • プロパティキーP(文字列)は、ToString(ToUint32(P))Pに等しく、ToUint32(P)が232−1に等しくない場合にのみ、配列インデックスです。これが何を意味するのかは、すぐに説明します。
  • キーが配列インデックスである配列プロパティは、要素と呼ばれます。

言い換えれば、仕様の世界では、括弧内のすべての値は文字列に変換され、数値であってもプロパティキーとして解釈されます。次のやり取りでこれを示します。

> var arr = ['a', 'b'];
> arr['0']
'a'
> arr[0]
'a'

配列インデックスであるためには、プロパティキーP(文字列!)は、次の計算の結果と等しくなければなりません。

  1. Pを数値に変換します。
  2. 数値を32ビット符号なし整数に変換します。
  3. 整数を文字列に変換します。

つまり、配列インデックスは、32ビット範囲0 ≤ i < 232−1の文字列化された整数iでなければなりません。上限は、(前に引用したように)仕様で明示的に除外されています。最大長の予約済みです。この定義がどのように機能するかを確認するために、「ビット単位演算子による32ビット整数」の関数ToUint32()を使用しましょう。

まず、数値を含まない文字列は常に0に変換されます。これは、文字列化後、文字列と等しくありません。

> ToUint32('xyz')
0
> ToUint32('?@#!')
0

次に、範囲外の文字列化された整数も、完全に異なる整数に変換されます。これは、文字列化後、文字列と等しくありません。

> ToUint32('-1')
4294967295
> Math.pow(2, 32)
4294967296
> ToUint32('4294967296')
0

第三に、文字列化された非整数数値は整数に変換されます。これもまた異なります。

> ToUint32('1.371')
1

仕様では、配列インデックスに指数がないことも強制されています。

> ToUint32('1e3')
1000

また、先頭にゼロがないことも強制されています。

> var arr = ['a', 'b'];
> arr['0']  // array index
'a'
> arr['00'] // normal property
undefined

length

lengthプロパティの基本的な機能は、配列内の最大のインデックスを追跡することです。

> [ 'a', 'b' ].length
2
> [ 'a', , 'b' ].length
3

したがって、lengthは要素の数をカウントしないため、独自の関数を作成する必要があります。例:

function countElements(arr) {
    var elemCount = 0;
    arr.forEach(function () {
        elemCount++;
    });
    return elemCount;
}

要素(非穴)をカウントするために、forEachが穴をスキップするという事実を使用しました。これがインタラクションです。

> countElements([ 'a', 'b' ])
2
> countElements([ 'a', , 'b' ])
2

配列の長さを手動で増やす

配列の長さを手動で増やしても、配列にほとんど影響はありません。穴が作成されるだけです。

> var arr = [ 'a', 'b' ];
> arr.length = 3;
> arr  // one hole at the end
[ 'a', 'b', ,]

最後の結果には末尾に2つのカンマがあります。これは、末尾のカンマがオプションであり、常に無視されるためです。

先ほど行った操作では、要素は追加されませんでした。

> countElements(arr)
2

ただし、lengthプロパティは、新しい要素を挿入する場所を示すポインターとして機能します。例:

> arr.push('c')
4
> arr
[ 'a', 'b', , 'c' ]

したがって、Arrayコンストラクターを使用して配列の初期長さを設定すると、完全に空の配列が作成されます。

> var arr = new Array(2);
> arr.length
2
> countElements(arr)
0

配列の長さを短くする

配列の長さを短くすると、新しい長さ以上のすべての要素が削除されます。

> var arr = [ 'a', 'b', 'c' ];
> 1 in arr
true
> arr[1]
'b'

> arr.length = 1;
> arr
[ 'a' ]
> 1 in arr
false
> arr[1]
undefined

配列のクリア

配列の長さを0に設定すると、空になります。これにより、他の人のために配列をクリアできます。例:

function clearArray(arr) {
    arr.length = 0;
}

これがインタラクションです。

> var arr = [ 'a', 'b', 'c' ];
> clearArray(arr)
> arr
[]

ただし、このアプローチは、各配列要素が明示的に削除されるため、遅くなる可能性があります。皮肉なことに、新しい空の配列を作成する方が速いことがよくあります。

arr = [];

共有配列のクリア

配列の長さをゼロに設定すると、配列を共有するすべての人に影響を与えるという事実に注意する必要があります。

> var a1 = [1, 2, 3];
> var a2 = a1;
> a1.length = 0;

> a1
[]
> a2
[]

対照的に、空の配列を割り当てることはありません。

> var a1 = [1, 2, 3];
> var a2 = a1;
> a1 = [];

> a1
[]
> a2
[ 1, 2, 3 ]

最大長

配列の最大長は232−1です。

> var arr1 = new Array(Math.pow(2, 32));  // not ok
RangeError: Invalid array length

> var arr2 = new Array(Math.pow(2, 32)-1);  // ok
> arr2.push('x');
RangeError: Invalid array length

配列の穴

配列とは、インデックスから値へのマップです。つまり、配列には、つまり、配列内に欠落している長さよりも小さいインデックスが存在する可能性があります。それらのインデックスの1つにある要素を読み取ると、undefinedが返されます。

ヒント

配列の穴を避けることをお勧めします。JavaScriptはそれらを矛盾して処理します(つまり、一部のメソッドはそれらを無視し、その他のメソッドは無視しません)。ありがたいことに、通常、穴がどのように処理されるかを知る必要はありません。それらはめったに役に立たず、パフォーマンスに悪影響を与えます。

穴を作成する

配列のインデックスに割り当てることで、穴を作成できます。

> var arr = [];
> arr[0] = 'a';
> arr[2] = 'c';
> 1 in arr  // hole at index 1
false

配列リテラルで値を省略することでも、穴を作成できます。

> var arr = ['a',,'c'];
> 1 in arr  // hole at index 1
false

警告

末尾の穴を作成するには、末尾のカンマを2つ必要とする必要があります。なぜなら、最後のカンマは常に無視されるためです。

> [ 'a', ].length
1
> [ 'a', ,].length
2

スパース配列とデンス配列

このセクションでは、要素としての穴とundefinedの違いについて検討します。穴を読み取るとundefinedが返されるため、両者は非常によく似ています。

穴のある配列はスパースと呼ばれます。穴のない配列はと呼ばれます。密な配列は連続しており、ゼロから始まり、length − 1で終わる各インデックスに要素があります。スパース配列と密な配列という、次の2つの配列を比較してみましょう。これら2つは非常によく似ています。

var sparse = [ , , 'c' ];
var dense  = [ undefined, undefined, 'c' ];

穴は、ほとんど同じインデックスに要素undefinedがあるようなものです。どちらの配列も同じ長さを持っています。

> sparse.length
3
> dense.length
3

しかし、スパース配列はインデックス0に要素を持っていません。

> 0 in sparse
false
> 0 in dense
true

forによる反復は、両方の配列で同じです。

> for (var i=0; i<sparse.length; i++) console.log(sparse[i]);
undefined
undefined
c
> for (var i=0; i<dense.length; i++) console.log(dense[i]);
undefined
undefined
c

forEachによる反復は、穴をスキップしますが、undefined要素はスキップしません。

> sparse.forEach(function (x) { console.log(x) });
c
> dense.forEach(function (x) { console.log(x) });
undefined
undefined
c

どの操作が穴を無視し、どの操作が穴を考慮するのか?

配列に関する操作の中には、穴を無視するものと、穴を考慮するものがあります。このセクションでは、詳細を説明します。

配列の反復メソッド

forEach()穴をスキップします。

> ['a',, 'b'].forEach(function (x,i) { console.log(i+'.'+x) })
0.a
2.b

every()穴をスキップします(同様に、some()も)。

> ['a',, 'b'].every(function (x) { return typeof x === 'string' })
true

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

> ['a',, 'b'].map(function (x,i) { return i+'.'+x })
[ '0.a', , '2.b' ]

filter()穴を削除します。

> ['a',, 'b'].filter(function (x) { return true })
[ 'a', 'b' ]

その他の配列メソッド

join()は穴、undefined、およびnull空の文字列に変換します。

> ['a',, 'b'].join('-')
'a--b'
> [ 'a', undefined, 'b' ].join('-')
'a--b'

sort()はソート中に穴を保持します。

> ['a',, 'b'].sort()  // length of result is 3
[ 'a', 'b', ,  ]

for-inループ

for-inループは、プロパティキー(これは配列インデックスのスーパーセットです)を正しくリストします。

> for (var key in ['a',, 'b']) { console.log(key) }
0
2

Function.prototype.apply()

apply()は、各穴を値がundefinedの引数に変換します。次の相互作用がこれを示しています。関数f()は、引数を配列として返します。apply()に3つの穴を持つ配列を渡してf()を呼び出すと、後者は3つのundefined引数を受け取ります。

> function f() { return [].slice.call(arguments) }
> f.apply(null, [ , , ,])
[ undefined, undefined, undefined ]

つまり、apply()を使用して、undefinedを持つ配列を作成できます。

> Array.apply(null, Array(3))
[ undefined, undefined, undefined ]

警告

apply()は、空の配列の穴をundefinedに変換しますが、穴を含んでいる可能性がある任意配列の穴を埋めるために使用することはできません。たとえば、任意の配列[2]を考えてみましょう。

> Array.apply(null, [2])
[ , ,]

配列には穴が含まれていないため、apply()は同じ配列を返すはずです。代わりに、長さ2の空の配列(中身はすべて2つの穴)を返します。これは、Array()が単一の数値を配列要素としてではなく、配列の長さとして解釈するためです。

配列から穴を削除する

すでに見たように、filter()穴を削除します。

> ['a',, 'b'].filter(function (x) { return true })
[ 'a', 'b' ]

任意の配列の穴をundefinedに変換するためのカスタム関数を使用します。

function convertHolesToUndefineds(arr) {
    var result = [];
    for (var i=0; i < arr.length; i++) {
        result[i] = arr[i];
    }
    return result;
}

関数の使用

> convertHolesToUndefineds(['a',, 'b'])
[ 'a', undefined, 'b' ]

要素の追加と削除(破壊的)

このセクションのすべてのメソッドは破壊的です。

Array.prototype.shift()

インデックス0の要素を削除して返します。後続の要素のインデックスは1ずつデクリメントされます。

> var arr = [ 'a', 'b' ];
> arr.shift()
'a'
> arr
[ 'b' ]
Array.prototype.unshift(elem1?, elem2?, ...)

指定された要素を配列の先頭に追加します。新しい長さを返します。

> var arr = [ 'c', 'd' ];
> arr.unshift('a', 'b')
4
> arr
[ 'a', 'b', 'c', 'd' ]
Array.prototype.pop()

配列の最後の要素を削除して返します。

> var arr = [ 'a', 'b' ];
> arr.pop()
'b'
> arr
[ 'a' ]
Array.prototype.push(elem1?, elem2?, ...)

指定された要素を配列の末尾に追加します。新しい長さを返します。

> var arr = [ 'a', 'b' ];
> arr.push('c', 'd')
4
> arr
[ 'a', 'b', 'c', 'd' ]

apply()Function.prototype.apply(thisValue, argArray)を参照)を使用すると、配列arr2を別の配列arr1に破壊的に追加できます。

> var arr1 = [ 'a', 'b' ];
> var arr2 = [ 'c', 'd' ];

> Array.prototype.push.apply(arr1, arr2)
4
> arr1
[ 'a', 'b', 'c', 'd' ]
Array.prototype.splice(start, deleteCount?, elem1?, elem2?, ...)

start始まり、deleteCount個の要素を削除し、指定された要素を挿入します。言い換えれば、位置startdeleteCount個の要素をelem1elem2などで置き換えることになります。このメソッドは、削除された要素を返します。

> var arr = [ 'a', 'b', 'c', 'd' ];
> arr.splice(1, 2, 'X');
[ 'b', 'c' ]
> arr
[ 'a', 'X', 'd' ]

特別なパラメータ値

  • startは負の値にすることができ、その場合は長さに追加されて開始インデックスが決定されます。したがって、-1は最後の要素を参照します。
  • deleteCountはオプションです。省略した場合(およびそれに続くすべての引数も)、インデックスstart以降のすべての要素が削除されます。

この例では、最後から2番目のインデックス以降のすべての要素を削除します。

> var arr = [ 'a', 'b', 'c', 'd' ];
> arr.splice(-2)
[ 'c', 'd' ]
> arr
[ 'a', 'b' ]

要素のソートと反転(破壊的)

これらのメソッドも破壊的です。

Array.prototype.reverse()

配列内の要素の順序を反転し、元の(変更された)配列への参照を返します。

> var arr = [ 'a', 'b', 'c' ];
> arr.reverse()
[ 'c', 'b', 'a' ]
> arr // reversing happened in place
[ 'c', 'b', 'a' ]
Array.prototype.sort(compareFunction?)

配列をソートして返します。

> var arr = ['banana', 'apple', 'pear', 'orange'];
> arr.sort()
[ 'apple', 'banana', 'orange', 'pear' ]
> arr  // sorting happened in place
[ 'apple', 'banana', 'orange', 'pear' ]

ソートでは値を文字列に変換して比較することに注意してください。つまり、数値は数値的にソートされないことを意味します。

> [-1, -20, 7, 50].sort()
[ -1, -20, 50, 7 ]

ソート方法を制御するオプションのパラメータcompareFunctionを指定することで、これを修正できます。次のシグネチャを持ちます。

function compareFunction(a, b)

この関数は、abを比較して、次を返します。

  • abより小さい場合は、ゼロより小さい整数(例:-1
  • abと等しい場合は、ゼロ
  • abより大きい場合は、ゼロより大きい整数(例:1

数値の比較

数値の場合、単純にa-bを返すことができますが、これにより数値オーバーフローが発生する可能性があります。それを防ぐには、より冗長なコードが必要になります。

function compareCanonically(a, b) {
    if (a < b) {
        return -1;
    } else if (a > b) {
        return 1;
    } else {
        return 0;
    }
}

私はネストされた条件演算子が好きではありません。しかし、この場合、コードが非常に簡潔になるため、お勧めしたい誘惑にかられます。

function compareCanonically(a, b) {
    return a < b ? -1 : (a > b ? 1 : 0);
}

関数の使用

> [-1, -20, 7, 50].sort(compareCanonically)
[ -20, -1, 7, 50 ]

文字列の比較

文字列の場合、String.prototype.localeCompareを使用できます(文字列の比較を参照)

> ['c', 'a', 'b'].sort(function (a,b) { return a.localeCompare(b) })
[ 'a', 'b', 'c' ]

オブジェクトの比較

パラメータcompareFunctionは、オブジェクトのソートにも役立ちます。

var arr = [
    { name: 'Tarzan' },
    { name: 'Cheeta' },
    { name: 'Jane' } ];

function compareNames(a,b) {
    return a.name.localeCompare(b.name);
}

比較関数としてcompareNamesを使用すると、arrnameでソートされます。

> arr.sort(compareNames)
[ { name: 'Cheeta' },
  { name: 'Jane' },
  { name: 'Tarzan' } ]

連結、スライス、結合(非破壊的)

次のメソッドは、配列に対してさまざまな非破壊的操作を実行します。

Array.prototype.concat(arr1?, arr2?, ...)

レシーバーのすべての要素の後に、配列arr1のすべての要素などが続く新しい配列を作成します。パラメータの1つが配列でない場合は、要素として結果に追加されます(たとえば、最初の引数の'c')。

> var arr = [ 'a', 'b' ];
> arr.concat('c', ['d', 'e'])
[ 'a', 'b', 'c', 'd', 'e' ]

concat()が呼び出される配列は変更されません。

> arr
[ 'a', 'b' ]
Array.prototype.slice(begin?, end?)

配列の要素を、beginで始まり、endの要素を含まない新しい配列にコピーします。

> [ 'a', 'b', 'c', 'd' ].slice(1, 3)
[ 'b', 'c' ]

endがない場合は、配列の長さが使用されます。

> [ 'a', 'b', 'c', 'd' ].slice(1)
[ 'b', 'c', 'd' ]

両方のインデックスがない場合、配列がコピーされます。

> [ 'a', 'b', 'c', 'd' ].slice()
[ 'a', 'b', 'c', 'd' ]

いずれかのインデックスが負の場合、配列の長さがそれに追加されます。したがって、-1は最後の要素を参照します。

> [ 'a', 'b', 'c', 'd' ].slice(1, -1)
[ 'b', 'c' ]
> [ 'a', 'b', 'c', 'd' ].slice(-2)
[ 'c', 'd' ]
Array.prototype.join(separator?)

すべての配列要素にtoString()を適用し、結果の間にseparatorの文字列を挿入することで文字列を作成します。separatorが省略されている場合は、','が使用されます。

> [3, 4, 5].join('-')
'3-4-5'
> [3, 4, 5].join()
'3,4,5'
> [3, 4, 5].join('')
'345'

join()は、undefinednullを空の文字列に変換します。

> [undefined, null].join('#')
'#'

配列内の穴も空の文字列に変換されます。

> ['a',, 'b'].join('-')
'a--b'

値の検索(非破壊的)

次のメソッドは、配列内の値を検索します。

Array.prototype.indexOf(searchValue, startIndex?)

startIndexから始まる配列でsearchValueを検索します。最初に出現したインデックスを返し、何も見つからない場合は-1を返します。startIndexが負の場合は、配列の長さが追加されます。省略した場合は、配列全体が検索されます。

> [ 3, 1, 17, 1, 4 ].indexOf(1)
1
> [ 3, 1, 17, 1, 4 ].indexOf(1, 2)
3

厳密な等価性(等価演算子:===と==を参照)が検索に使用されます。つまり、indexOf()NaNを見つけることができません。

> [NaN].indexOf(NaN)
-1
Array.prototype.lastIndexOf(searchElement, startIndex?)

startIndexから始まる配列で、searchElementを逆方向に検索します。最初に出現したインデックスを返し、何も見つからない場合は-1を返します。startIndexが負の場合は、配列の長さが追加されます。省略した場合は、配列全体が検索されます。厳密な等価性(等価演算子:===と==を参照)が検索に使用されます。

> [ 3, 1, 17, 1, 4 ].lastIndexOf(1)
3
> [ 3, 1, 17, 1, 4 ].lastIndexOf(1, -3)
1

反復(非破壊的)

反復メソッドは、関数を使用して配列を反復処理します。非破壊的な3種類の反復メソッドを区別します:検査メソッドは主に配列の内容を観察します。変換メソッドはレシーバーから新しい配列を導出します。そして、縮小メソッドはレシーバーの要素に基づいて結果を計算します。

検査メソッド

このセクションで説明する各メソッドは、次のようになります。

arr.examinationMethod(callback, thisValue?)

このようなメソッドは、以下のパラメータを取ります。

  • callback は最初のパラメータで、呼び出す関数です。検査メソッドに応じて、コールバックは boolean を返すか、何も返しません。それは以下のシグネチャを持ちます。

    function callback(element, index, array)

    elementcallback が処理する配列要素、index は要素のインデックス、そして arrayexaminationMethod が呼び出された配列です。

  • thisValuecallback 内の this の値を設定できます。

そして、私が今説明したシグネチャを持つ検査メソッドです。

Array.prototype.forEach(callback, thisValue?)

配列の要素を反復処理します

var arr = [ 'apple', 'pear', 'orange' ];
arr.forEach(function (elem) {
    console.log(elem);
});
Array.prototype.every(callback, thisValue?)

コールバックがすべての要素に対して true を返すと true を返します。コールバックが false を返すとすぐに反復処理を停止します。値を返さないと、暗黙的に undefined が返され、every() はこれを false と解釈することに注意してください。every() は全称量化子(「すべてについて」)のように機能します。

この例では、配列内のすべての数値が偶数であるかどうかをチェックします。

> function isEven(x) { return x % 2 === 0 }
> [ 2, 4, 6 ].every(isEven)
true
> [ 2, 3, 4 ].every(isEven)
false

配列が空の場合、結果は true になります(そして callback は呼び出されません)。

> [].every(function () { throw new Error() })
true
Array.prototype.some(callback, thisValue?)

コールバックが少なくとも1つの要素に対して true を返すと true を返します。コールバックが true を返すとすぐに反復処理を停止します。値を返さないと、暗黙的に undefined が返され、some はこれを false と解釈することに注意してください。some() は存在量化子(「少なくとも1つ存在する」)のように機能します。

この例では、配列に偶数があるかどうかをチェックします。

> function isEven(x) { return x % 2 === 0 }
> [ 1, 3, 5 ].some(isEven)
false
> [ 1, 2, 3 ].some(isEven)
true

配列が空の場合、結果は false になります(そして callback は呼び出されません)。

> [].some(function () { throw new Error() })
false

forEach() の潜在的な落とし穴の1つは、ループを早期に中断するために break や類似のものをサポートしていないことです。それが必要な場合は、some() を使用できます。

function breakAtEmptyString(strArr) {
    strArr.some(function (elem) {
        if (elem.length === 0) {
            return true; // break
        }
        console.log(elem);
        // implicit: return undefined (interpreted as false)
    });
}

some() は、中断が発生した場合に true を返し、それ以外の場合は false を返します。これにより、(for ループでは少しトリッキーな)反復が正常に終了したかどうかによって異なる反応をすることができます。

変換メソッド

変換メソッドは入力配列を取り、出力配列を生成します。その間、コールバックが出力の生成方法を制御します。コールバックは、検査と同じシグネチャを持ちます。

function callback(element, index, array)

変換メソッドは2つあります。

Array.prototype.map(callback, thisValue?)

各出力配列要素は、入力要素に callback を適用した結果です。例えば

> [ 1, 2, 3 ].map(function (x) { return 2 * x })
[ 2, 4, 6 ]
Array.prototype.filter(callback, thisValue?)

出力配列には、callbacktrue を返す入力要素のみが含まれます。例えば:

> [ 1, 0, 3, 0 ].filter(function (x) { return x !== 0 })
[ 1, 3 ]

縮約メソッド

縮約の場合、コールバックは異なるシグネチャを持ちます。

function callback(previousValue, currentElement, currentIndex, array)

パラメータ previousValue は、コールバックによって以前に返された値です。コールバックが最初に呼び出されるとき、2つの可能性があります(説明は Array.prototype.reduce() の場合です。reduceRight() との違いは括弧内に記載されています)。

  • 明示的な initialValue が提供されています。この場合、previousValueinitialValue であり、currentElement は最初の配列要素になります(reduceRight:最後の配列要素)。
  • 明示的な initialValue は提供されていません。この場合、previousValue は最初の配列要素であり、currentElement は2番目の配列要素になります(reduceRight:最後の配列要素と最後から2番目の配列要素)。

縮約メソッドは2つあります。

Array.prototype.reduce(callback, initialValue?)

から右に反復処理し、前に概説したようにコールバックを呼び出します。メソッドの結果は、コールバックによって最後に返された値です。この例では、すべての配列要素の合計を計算します。

function add(prev, cur) {
    return prev + cur;
}
console.log([10, 3, -1].reduce(add)); // 12

単一の要素を持つ配列で reduce を呼び出した場合、その要素が返されます。

> [7].reduce(add)
7

空の配列で reduce を呼び出した場合、initialValue を指定する必要があります。そうしないと、例外が発生します。

> [].reduce(add)
TypeError: Reduce of empty array with no initial value
> [].reduce(add, 123)
123
Array.prototype.reduceRight(callback, initialValue?)
reduce() と同様に機能しますが、右から左に反復処理します。

注意

多くの関数型プログラミング言語では、reducefold または foldl (左畳み込み)として知られており、reduceRightfoldr (右畳み込み)として知られています。

reduce メソッドの別の見方は、それが n 項演算子 OP を実装しているということです。

OP1≤i≤n xi

二項演算子 op2 の一連の適用を介して

(...(x1 op2 x2) op2 ...) op2 xn

これは前のコード例で発生したことです。JavaScript の二項プラス演算子を介して、配列の n 項和演算子を実装しました。

例として、次の関数を介して 2 つの反復方向を調べてみましょう。

function printArgs(prev, cur, i) {
    console.log('prev:'+prev+', cur:'+cur+', i:'+i);
    return prev + cur;
}

予想どおり、reduce() は左から右に反復処理します。

> ['a', 'b', 'c'].reduce(printArgs)
prev:a, cur:b, i:1
prev:ab, cur:c, i:2
'abc'
> ['a', 'b', 'c'].reduce(printArgs, 'x')
prev:x, cur:a, i:0
prev:xa, cur:b, i:1
prev:xab, cur:c, i:2
'xabc'

そして、reduceRight() は右から左に反復処理します。

> ['a', 'b', 'c'].reduceRight(printArgs)
prev:c, cur:b, i:1
prev:cb, cur:a, i:0
'cba'
> ['a', 'b', 'c'].reduceRight(printArgs, 'x')
prev:x, cur:c, i:2
prev:xc, cur:b, i:1
prev:xcb, cur:a, i:0
'xcba'

落とし穴: 配列のようなオブジェクト

JavaScript の一部のオブジェクトは配列のように見えますが、配列ではありません。これは通常、インデックス付きアクセスと length プロパティを持っているが、配列メソッドがないことを意味します。例としては、特殊変数 arguments、DOM ノードリスト、および文字列などがあります。配列のようなオブジェクトと汎用メソッドは、配列のようなオブジェクトを操作するためのヒントを提供します。

ベストプラクティス:配列の反復処理

配列 arr を反復処理するには、2つのオプションがあります。

  • 単純な for ループ (for を参照)

    for (var i=0; i<arr.length; i++) {
        console.log(arr[i]);
    }
  • 配列反復メソッドのいずれか (反復 (非破壊的)を参照)。たとえば、forEach()

    arr.forEach(function (elem) {
        console.log(elem);
    });

配列を反復処理するために for-in ループ (for-in を参照)を使用しないでください。これは値ではなく、インデックスを反復処理します。また、継承されたものも含め、通常のプロパティのキーを含みます。

次へ:19. 正規表現