この投稿は インタープリズムの面々が、普段の業務に役立つ記事を丹精込めて書き上げる! Advent Calendar 2016 - Qiitaの5日目 の記事です。
こんにちは、imamotoです。
JavaScriptの配列関数について、2回に渡って整理するシリーズの後編になります。
前編はこちらをご覧ください。
前編では以下の配列関数について説明しました。
1. static関数
ES7項番 | 関数 | 戻り値 | バージョン |
---|---|---|---|
22.1.2.2 | Array.isArray ( arg ) | Boolean | ES5 |
22.1.2.1 | Array.from ( items [ , mapfn [ , thisArg ] ] ) | Array | ES6 |
22.1.2.3 | Array.of ( ...items ) | Array | ES6 |
2. 配列をコピーせずに要素を追加・削除する関数
ES7項番 | 関数 | 戻り値 | バージョン |
---|---|---|---|
22.1.3.18 | Array.prototype.push ( ...items ) | 新しいlength | - |
22.1.3.29 | Array.prototype.unshift ( ...items ) | 新しいlength | - |
22.1.3.17 | Array.prototype.pop ( ) | 最後の要素 | - |
22.1.3.22 | Array.prototype.shift ( ) | 先頭の要素 | - |
3. 配列をコピーして要素を追加・削除する関数
ES7項番 | 関数 | 戻り値 | バージョン |
---|---|---|---|
22.1.3.1 | Array.prototype.concat ( ...arguments ) | Array | - |
22.1.3.23 | Array.prototype.slice (start, end) | Array | - |
4. ある配列から特定のIteratorを取得する関数
ES7項番 | 関数 | 戻り値 | バージョン |
---|---|---|---|
22.1.3.14 | Array.prototype.keys ( ) | Iterator | ES6 |
22.1.3.30 | Array.prototype.values ( ) | Iterator | ES6 |
22.1.3.4 | Array.prototype.entries ( ) | Iterator | ES6 |
後編では、前編で紹介しきれなかった残りの配列関数についてまとめていきたいと思います。
少し長く情報も多いですが、最後まで読んでいただけると幸いです。
それじゃ引き続き、イカした関数を紹介していくぜ!!
5. すべての要素にコールバックを実行する関数
ES7項番 | 関数 | 戻り値 | バージョン |
---|---|---|---|
22.1.3.10 | Array.prototype.forEach ( callbackfn [ , thisArg ] ) | void | ES5 |
22.1.3.7 | Array.prototype.filter ( callbackfn [ , thisArg ] ) | Array | ES5 |
22.1.3.16 | Array.prototype.map ( callbackfn [ , thisArg ] ) | Array | ES5 |
22.1.3.19 | Array.prototype.reduce ( callbackfn [ , initialValue ] ) | Any | ES5 |
22.1.3.20 | Array.prototype.reduceRight ( callbackfn [ , initialValue ] ) | Any | ES5 |
引数のコールバックを配列のすべての要素に対して実行する関数たち
- このグループでは「引数で受け取ったコールバック関数を、配列のすべての要素について実行する関数」5つをまとめました。
- すべてES5で追加された、非常に便利な関数です。
- 戻り値の種類に応じて更に以下の3タイプに分かれます。
- コールバックを実行するだけの関数
- コールバックを実行して新たな配列を取得する関数
- コールバックのすべての実行結果から新たな値を取得する関数
コールバックを実行するだけの "forEach"
Array.prototype.forEach ( callbackfn [ , thisArg ] )
- 配列の要素について、第一引数のコールバック関数
callbackfn
を先頭から順に実行します。- このコールバック関数には
配列の値, 配列のインデックス, 配列そのもの
の3つの引数を与えられます。
- このコールバック関数には
- 第二引数の
thisArg
でコールバック関数内のthis
を指すオブジェクトをバインドできます。
- 配列の要素について、第一引数のコールバック関数
[1, 2, 3].forEach(function(value, index, array) { console.log('length: ' + array.length + ', index: ' + index + ', value: ' + value); }); // length: 3, index: 0, value: 1 // length: 3, index: 1, value: 2 // length: 3, index: 2, value: 3
コールバックを実行して新たな配列を取得する "filter", "map"
- "filter"と"map"は、前述した"forEach"と同じ引数を受け取ります(コールバック関数の引数も同じです)
Array.prototype.filter ( callbackfn [ , thisArg ] )
- 配列の要素について、第一引数のコールバック関数
callbackfn
を先頭から順に実行し、戻り値がtrueである要素だけを持つ新たな配列を取得します。- このコールバック関数には
配列の値, 配列のインデックス, 配列そのもの
の3つの引数を与えられます。
- このコールバック関数には
- 第二引数の
thisArg
でコールバック関数内のthis
を指すオブジェクトをバインドできます。
- 配列の要素について、第一引数のコールバック関数
Array.prototype.map ( callbackfn [ , thisArg ] )
- 配列の要素について、第一引数のコールバック関数
callbackfn
を先頭から順に実行し、その実行結果で構成された新たな配列を取得します。- このコールバック関数には
配列の値, 配列のインデックス, 配列そのもの
の3つの引数を与えられます。
- このコールバック関数には
- 第二引数の
thisArg
でコールバック関数内のthis
を指すオブジェクトをバインドできます。
- 配列の要素について、第一引数のコールバック関数
var array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; // filterで偶数だけ取得 var evenArray = array.filter(function(value) {return value % 2 === 0;}); // mapですべての要素を2乗 var evenSquareArray = evenArray.map(function(value) {return value * value;}); // forEachで出力 evenSquareArray.forEach(function(value) {console.log(value);}); // 4 // 16 // 36 // 64 // 100
コールバックのすべての実行結果から新たな値を取得する "reduce", "reduceRight"
Array.prototype.reduce ( callbackfn [ , initialValue ] )
- 配列の要素について、第一引数のコールバック関数
callbackfn
を先頭から順に実行して、1つの値を返却します。- このコールバック関数には
1つ前の実行結果, 現在の配列の値, 配列のインデックス, 配列そのもの
の4つの引数を与えられます。
- このコールバック関数には
- 第二引数がある場合は、コールバック関数の引数「1つ前の実行結果」の初期値となります。
- 第二引数がない場合は、コールバック関数の引数「1つ前の実行結果」の初期値は配列の先頭の要素の値になります。
- この場合「現在の配列の値」「配列のインデックス」は配列の2つ目の要素になります。
- 配列の要素について、第一引数のコールバック関数
Array.prototype.reduceRight ( callbackfn [ , initialValue ] )
- 配列の要素について、第一引数のコールバック関数
callbackfn
を末尾から順に実行して、1つの値を返却します。 - それ以外は "reduce" と同じです。
- 配列の要素について、第一引数のコールバック関数
// 第二引数の初期値がある場合 [1, 2, 3].reduce(function(prev, current, index, array) { console.log('prev: ' + prev + ', current: ' + current + ', index: ' + index); return prev + current; }, 10); // prev: 10, current: 1, index: 0 // prev: 11, current: 2, index: 1 // prev: 13, current: 3, index: 2 // 16 // 第二引数の初期値がない場合 [1, 2, 3].reduce(function(prev, current, index, array) { console.log('prev: ' + prev + ', current: ' + current + ', index: ' + index); return prev + current; }); // prev: 1, current: 2, index: 1 // prev: 3, current: 3, index: 2 // 6 // reduceとreduceRightの比較 var combineString = function(prev, current) { return prev + ' ' + current; }; ["Hello","Array","World"].reduce(combineString); // "Hello Array World" ["Hello","Array","World"].reduceRight(combineString); // "World Array Hello"
[余談] "map", "filter", "forEach" とアロー関数の相性が良い理由
- ES6から新たに追加されたアロー関数は関数が短く書けてとても便利です。
map
とfilter
は配列が戻り値なので、このアロー関数とは特に相性が良いように思います。
// 先程の例もワンライナーで書ける [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].filter(v => v % 2 === 0).map(v => v * v).forEach(v => console.log(v)); // 4 // 16 // 36 // 64 // 100
- もう1つ、
map
,filter
,forEach
はアロー関数と相性が良い理由があります。 - それは以下の例のように、第二引数で指定するコールバック関数のthisへのバインドを省略できることにあります。
function Example() { // コンストラクタ関数のローカル変数 this.number = 10; // thisを保存 var self = this; // 1. selfを使ってnumberの値を参照 this.func1 = function() { [1, 2, 3].forEach(function(value) { console.log(self.number + value); }); }; // 2. 第二引数にthisをバインドする this.func2 = function() { [1, 2, 3].forEach(function(value) { console.log(this.number + value); }, this); }; // 3. コールバック関数をアロー関数にすると関数が定義された場所のthisを指す this.func3 = function() { [1, 2, 3].forEach(v => console.log(this.number + v)); }; // 誤った例:thisをバインドせずに直接コールバック関数内で呼び出す this.wrongFunc = function() { [1, 2, 3].forEach(function(value) { console.log(this.number + value); }); }; } var example = new Example(); // すべて同じ出力結果になる(11, 12, 13) example.func1(); example.func2(); example.func3(); // this.numberを参照できない(NaN, NaN, NaN) example.wrongFunc();
アロー関数についてはこちらもとても参考になりました。
6. 配列内の要素を検索する関数
ES7項番 | 関数 | 戻り値 | バージョン |
---|---|---|---|
22.1.3.5 | Array.prototype.every ( callbackfn [ , thisArg ] ) | Boolean | ES5 |
22.1.3.24 | Array.prototype.some ( callbackfn [ , thisArg ] ) | Boolean | ES5 |
22.1.3.8 | Array.prototype.find ( predicate [ , thisArg ] ) | value | ES6 |
22.1.3.9 | Array.prototype.findIndex ( predicate [ , thisArg ] ) | index | ES6 |
22.1.3.12 | Array.prototype.indexOf ( searchElement [ , fromIndex ] ) | index | ES5 |
22.1.3.15 | Array.prototype.lastIndexOf ( searchElement [ , fromIndex ] ) | index | ES5 |
22.1.3.11 | Array.prototype.includes ( searchElement [ , fromIndex ] ) | Boolean | ES7 |
色々な方法で配列内の要素を検索してくれる関数たち
- このグループでは「配列内の要素を探す関数」7つをまとめました。
- ES5から充実し始めており、唯一ES7で追加となった配列関数
includes
もこのグループに含まれます。 - 「何で探すか」と「何を返すか」で分類できます。
分類表
コールバック関数がtrueを返す要素を探す | 指定した値に一致する要素を探す | |
---|---|---|
当てはまったか否かをBooleanで返す | every (全部true), some (1つでもtrue) |
includes (1つでも一致) |
当てはまった値を返す | find (最初の値) |
なし |
当てはまったインデックスを返す | findIndex (最初のインデックス) |
indexOf (最初のインデックス), lastIndexOf (最後のインデックス) |
- コールバック関数がtrueを返す要素を探す関数
- 第一引数のコールバック関数は
配列の値, 配列のインデックス, 走査中の配列
の3つの引数を受け取ることができます。 - 第二引数ではコールバック関数のthisをバインドできます。
- 第一引数のコールバック関数は
- 指定した値に一致する要素を探す関数
- 第二引数に検索を開始するインデックスを指定できます。
[余談] 検索を開始するインデックスと配列走査の方向について
- 「指定した値に一致する要素を探す関数」3つについて、以下にそれぞれ特徴がある
- 検索を開始するインデックス(fromIndex)のデフォルトについて
- 配列走査の方向について
indexOf
,includes
- fromIndexのデフォルト値は
0
(配列全体を検索) - fromIndexが負の値の場合は配列の終わりからのオフセット
- fromIndexに負の値が設定されても、配列の走査は前から後ろ
- fromIndexのデフォルト値は
lastIndexOf
- fromIndexのデフォルト値は
array.length
- fromIndexが負の値の場合は配列の終わりからのオフセット
- fromIndexに負の値が設定されても、配列の走査は後ろから前
- fromIndexのデフォルト値は
7. 配列の要素を並び替える関数
ES7項番 | 関数 | 戻り値 | バージョン |
---|---|---|---|
22.1.3.21 | Array.prototype.reverse ( ) | void | - |
22.1.3.25 | Array.prototype.sort (comparefn) | void | - |
配列の要素を並び替えてくれる関数たち
- このグループでは「要素を並び替える関数」2つをまとめました。
2つとも破壊的な関数で、ES5よりも前から存在しています。
Array.prototype.reverse ( )
:配列を逆順にソートします。Array.prototype.sort (comparefn)
:引数のコールバック関数の戻り値に応じてソートします。- コールバック関数が指定されない場合は要素を文字列比較して辞書順にソートされます。
- コールバック関数が指定されている場合、関数は比較対象の2つの配列の要素を引数として受け取ります。
comparefn(a, b)
が負の値を返す場合、aはbよりも前(小さいインデックス)になります。comparefn(a, b)
が正の値を返す場合、aはbよりも後(大きいインデックス)になります。comparefn(a, b)
が0を返す場合、aとbの関係の変動はありません。
8. 配列の文字列表現
ES7項番 | 関数 | 戻り値 | バージョン |
---|---|---|---|
22.1.3.13 | Array.prototype.join (separator) | String | - |
22.1.3.27 | Array.prototype.toLocaleString ( [ reserved1 [ , reserved2 ] ] ) | String | - |
22.1.3.28 | Array.prototype.toString ( ) | String | - |
配列を文字列で表現する関数たち
- このグループでは「配列を文字列表現にする関数」3つをまとめました。
どの関数も特定の区切り文字で先頭から各要素を繋げる
Array.prototype.join (separator)
:指定した区切り文字で各要素を繋げた文字列を返却します。デフォルトは","(カンマ)。Array.prototype.toLocaleString ( [ reserved1 [ , reserved2 ] ] )
:各要素をロケールに応じた文字列に変換し、カンマやピリオドで区切った文字列を返却します。Array.prototype.toString ( )
:カンマ区切りの文字列を返却します。
9. 配列内の要素を更新・削除する関数
ES7項番 | 関数 | 戻り値 | バージョン |
---|---|---|---|
22.1.3.3 | Array.prototype.copyWithin (target, start [ , end ] ) | void | ES6 |
22.1.3.6 | Array.prototype.fill (value [ , start [ , end ] ] ) | void | ES6 |
22.1.3.26 | Array.prototype.splice (start, deleteCount, ...items ) | void | - |
配列の中身を変えちゃう関数たち
- このグループでは「配列内の要素を更新・削除する関数」3つをまとめました。
copyWithin
とfill
はES6で追加された新しい関数です。- 文字だけでの説明が難しいため、コードを見ながら1つずつ説明します。
自身の一部を自身の別の場所に貼り付ける "copyWithin"
Array.prototype.copyWithin (target, start [ , end ] )
- 配列の連続した要素を、その配列の別の場所から貼り付けて要素を上書きします。
- 既存の要素を上書きするので、配列の長さは変わりません。
target
:貼り付け先start
:コピー元開始位置(デフォルトは0)end
:コピー元終了位置(デフォルトは配列のlength)
// startとendはデフォルト値 // コピー元の長さがはみ出た場合も配列の長さは変わらない [1, 2, 3, 4, 5].copyWithin(3); // [1, 2, 3, 1, 2] // 負の値も設定できる [1, 2, 3, 4, 5].copyWithin(-2); // [1, 2, 3, 1, 2] // startとendにも負の値が設定できる [1, 2, 3, 4, 5].copyWithin(0, -2 ,-1); // [4, 2, 3, 4, 5]
同じ値で配列を埋め尽くす "fill"
Array.prototype.fill (value [ , start [ , end ] ] )
- 配列の開始位置と終了位置を指定して、すべて同じ値で要素を上書きします。
- 既存の要素を上書きするので、配列の長さは変わりません。
value
:配列に設定する値start
:開始位置(デフォルトは0)end
:終了位置(デフォルトは配列のlength)
// すべての値を上書きする [1, 2, 3].fill(4); // [4, 4, 4] // startとendに負の値が設定できる [1, 2, 3].fill(4, -3, -2); // [4, 2, 3] // 配列の長さと埋める値のみを指定する書き方 Array(3).fill(4); // [4, 4, 4]
要素の挿入も削除も自由自在な "splice"
Array.prototype.splice (start, deleteCount, ...items )
- 配列の特定の位置から複数の要素を削除と挿入を同時に行える関数です。
- 破壊的関数のため、元の配列が更新されます。
- 戻り値は、削除された値で構成された配列です。
start
:削除・挿入の開始位置deleteCount
:start
の位置から削除する要素の個数(デフォルトは配列のlength)...items
:start
の位置から挿入する要素(可変長)
// 開始位置のみ指定 var array1 = [1, 2, 3]; var delArray1 = array1.splice(2); array1; // [1, 2] 削除後の配列 delArray1; // [3] 削除された値で構成された配列 // 開始位置、削除個数、追加する要素を指定 var array2 = [1, 2, 3]; var delArray2 = array2.splice(1, 2, 3, 4, 5); array2; // [1, 3, 4, 5] delArray2; // [2, 3] // 配列の一部を別の配列で置き換える var array3 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; var replaceArray3 = [16, 17, 18, 19, 20]; var delArray3 = array3.splice(5, replaceArray3.length, ...replaceArray3); array3; // [1, 2, 3, 4, 5, 16, 17, 18, 19, 20] delArray3; // [6, 7, 8, 9, 10]
10. Well-known Symbolで参照される関数
ES7項番 | 関数 | 戻り値 | バージョン |
---|---|---|---|
22.1.3.31 | Array.prototype [ @@iterator ] ( ) | Iterator | ES6 |
最後はWell-known Symbolで参照される関数
- 最後のグループは「Well-known Symbolで参照される関数」です。1つしかありません。
- 関数自体の紹介の前に、
Symbol
について先にご紹介したいと思います。
そもそもSymbolとは何か
Symbol
とはES6から新たに追加されたプリミティブなデータ型で、値を生成する度に必ずユニークになるという特徴を持っています。- typeofの戻り値は
symbol
となる
// 値を生成 var sym1 = Symbol(); var sym2 = Symbol(); sym1 === sym2; // false // Symbolの説明を引数に渡すこともできます var sym3 = Symbol('これは3番目のSymbol'); typeof sym3; // symbol
Well-known Symbolとは何か
- EcmaScriptの仕様の中で定義されており、用途が定められている
Symbol
- Well-known Symbolは
Symbol.symbolName
という形で参照が可能 - EcmaScript上では
@@symbolName
と表記されるが、言語の文法上使える表現という訳ではない - 今回出て来る
Array.prototype [ @@iterator ] ( )
は、コード上ではArray.prototype[Symbol.iterator]()
と表現される
Symbol.iteratorについて
- Well-known Symbolの1つで、Iterableなオブジェクトは
Symbol.iterator
を必ずプロパティとして保持している Array.prototype[@@iterator]()
はArray.prototype.value()
と同じ関数を初期値として持つ
// iterator取得 var iterator = [1, 2][Symbol.iterator](); console.log(iterator.next()); // {value: 1, done: false} console.log(iterator.next()); // {value: 2, done: false} console.log(iterator.next()); // {value: undefined, done: true} // Array.prototype[Symbol.iterator]を上書きすると for 〜 of 構文が動かなくなる Array.prototype[Symbol.iterator] = function hoge(){}; for (let a of [1, 2, 3]) { console.log(a); }
[余談] Well-known Symbolが必要な理由は"互換性"
Array.prototype [ @@iterator ] ( )
はArray.prototype.value()
と同じ関数を指すが、for 〜 of
構文で呼び出されます。- もし仮に
Array.prototype.values()
をfor 〜 of
構文で呼び出す仕様にすると、Array.prototype.values
というプロパティで配列を拡張しているコードがあった際に、for 〜 of
構文そのものが動かなくなってしまいます。 - 必ずユニークになる
Symbol.iterator
というプロパティの関数を使うと、明示的に破壊する意図が無い限りはfor 〜 of
構文は使い続けられます。 Symbol
を理解するのにこちらの記事がとても参考になったので、是非読んでください。
[余談] Array.prototype.values()を実装するブラウザは非常に少ない
- 前編 の「[余談] "values" の実装率が低い理由」の項でも言及した通り、
Array.prototype.values()
は実装率がとても低いです。 - 一部のソフトウェアでの互換性が最も大きな理由ですが、
Array.prototype [ @@iterator ] ( )
があれば同じ関数を呼び出せる仕様のため、実装する必要も無いというのがもう一つ大きな理由なのでは無いかと思います。
参考ページ www.fxsitecompat.com
おわりに
2回に分けてJavaScriptの配列関数についてまとめてみました。
数が多すぎたり、情報を詰め込みすぎたりで読みにくい部分もあるかと思いますが、部分的にでも役に立つところがあれば幸いです。
最後まで読んでいただきありがとうございました。
インタープリズムの面々が、普段の業務に役立つ記事を丹精込めて書き上げる! Advent Calendar 2016 - Qiitaの6日目の記事