私たちはこのオープンソースプロジェクトを世界中の人々に提供したいと考えています。このチュートリアルの内容をあなたが知っている言語に翻訳するのを手伝ってください。

配列のメソッド

配列は多くのメソッドを提供します。分かりやすくするために、このチャプターではグループに分けて説明します。

アイテムの追加/削除

私たちは既に先頭または末尾にアイテムを追加/削除するメソッドを知っています:

  • push(...items)items を末尾に追加します。
  • pop() は末尾の要素を削除し、それを返します。
  • shift() は先頭の要素を削除し、それを返します。
  • unshift(...items) はアイテムを先頭に追加します。

他にもいくつかあります。

splice

配列から要素を削除する方法はどのようになるでしょう?

配列はオブジェクトなので、 delete を試すことができます:

let arr = ["I", "go", "home"];

delete arr[1]; // "go" を削除

alert( arr[1] ); // undefined

// now arr = ["I",  , "home"];
alert( arr.length ); // 3

要素は削除されましたが、配列は依然として3つの要素を持っており、arr.length == 3 となります。

これは自然なことです。なぜなら、 delete obj.keykey で値を削除するためのものです。それがすべてであり、オブジェクトでは問題ありません。しかし、通常配列では残りの要素が移動し、解放された場所を埋めたいです。今より短い配列になることを期待しています。

なので、特別なメソッドを使用する必要があります。

arr.splice(str) メソッドは、配列用のスイス製アーミーナイフです。それは何でもすることができます: 追加、削除、また要素の挿入も。

構文:

arr.splice(index[, deleteCount, elem1, ..., elemN])

位置 index から始まります。 deleteCount の要素を削除した後、その場所に elem1, ..., elemN を挿入します。このメソッドは削除した要素の配列を返します。

このメソッドは例で簡単に把握できます。

削除してみましょう:

let arr = ["I", "study", "JavaScript"];

arr.splice(1, 1); // インデックス 1 から 1 要素を削除

alert( arr ); // ["I", "JavaScript"]

簡単ですね。インデックス 1 から始まり、1 つ要素を削除します。

次の例では、3つの要素を削除し、他の2つの要素でそれらを置き換えます:

let arr = ["I", "study", "JavaScript", "right", "now"];

// 最初の 3 要素を削除し、別のものに置換
arr.splice(0, 3, "Let's", "dance");

alert( arr ) // 今は ["Let's", "dance", "right", "now"]

ここで、splice が削除された要素の配列を返していることを見ることができます:

let arr = ["I", "study", "JavaScript", "right", "now"];

// 最初の 2 要素を削除
let removed = arr.splice(0, 2);

alert( removed ); // "I", "study" <-- 削除された要素の配列

splice メソッドは削除せずに挿入することも可能です。そのためには、deleteCount0 をセットします:

let arr = ["I", "study", "JavaScript"];

// インデックス 2 から
// 削除 0
// その後 "complex" と "language" を挿入
arr.splice(2, 0, "complex", "language");

alert( arr ); // "I", "study", "complex", "language", "JavaScript"
負のインデックスも許容します

上記や他の配列のメソッドでは、負のインデックスが許容されます。それらは配列の末尾からの位置を指定します。:

let arr = [1, 2, 5];

// インデックス -1 (末尾から1つ前) から
// 削除 0 要素,
// その後 3 と 4 を挿入
arr.splice(-1, 0, 3, 4);

alert( arr ); // 1,2,3,4,5

slice

メソッド arr.slice は似たように見える arr.splice よりもはるかに単純です。

構文:

arr.slice(start, end)

開始インデックス "start" から "end" ("end" は含みません)のすべてのアイテムをコピーした新しい配列を返します。startend はともに負値になることができます。そのときは、配列の末尾からの位置が想定されます。

str.slice のように動作しますが、部分文字列の代わりに部分配列を作ります。

例:

let arr = ["t", "e", "s", "t"];

alert( arr.slice(1, 3) ); // e,s (1 から 3 をコピー)

alert( arr.slice(-2) ); // s,t (-2 から末尾まで)

引数なし arr.slice() でも呼び出すことができ、これは arr のコピーを生成します。これは、オリジナルの配列に影響を与えない形でさらに変換するためのためのコピーを取得するのによく使用されます。

concat

メソッド arr.concat は配列を他の配列またはアイテムと結合します。

構文:

arr.concat(arg1, arg2...)

任意の数の引数(配列または値)を許容します。

結果は arr, 次に arg1, arg2 などのアイテムを含む新しい配列を返します。

もし、引数が配列、もしくは Symbol.isConcatSpreadable プロパティを持っている場合、その全ての要素がコピーされます。そうでない場合、引数自体がコピーされます。

例:

let arr = [1, 2];

// arr と [3,4] をマージ
alert( arr.concat([3, 4])); // 1,2,3,4

// arr と [3,4] と [5,6] をマージ
alert( arr.concat([3, 4], [5, 6])); // 1,2,3,4,5,6

// arr と [3,4] をマージ後, 5 と 6 を追加
alert( arr.concat([3, 4], 5, 6)); // 1,2,3,4,5,6

通常は、配列から要素をコピーするだけです。それ以外のオブジェクトでは、配列のように見えたとしても、全体として追加されます:

let arr = [1, 2];

let arrayLike = {
  0: "something",
  length: 1
};

alert( arr.concat(arrayLike) ); // 1,2,[object Object]

…しかし、もし配列のようなオブジェクトが Symbol.isConcatSpreadable プロパティを持つ場合、concat は配列として扱います。つまり、代わりにその要素が追加されます:

let arr = [1, 2];

let arrayLike = {
  0: "something",
  1: "else",
  [Symbol.isConcatSpreadable]: true,
  length: 2
};

alert( arr.concat(arrayLike) ); // 1,2,something,else

イテレート/反復: forEach

arr.forEach メソッドは配列の全要素に対して関数を実行することができます。

構文:

arr.forEach(function(item, index, array) {
  // ... item に対して何か処理をする
});

例えば、これは配列の各要素を表示します:

// 各要素は alert を呼び出す
["Bilbo", "Gandalf", "Nazgul"].forEach(alert);

そしてこのコードは、ターゲットとなる配列内の位置についてより細かいです。

["Bilbo", "Gandalf", "Nazgul"].forEach((item, index, array) => {
  alert(`${item} is at index ${index} in ${array}`);
});

関数の結果(もし何かを返す場合)は捨てられ、無視されます。

配列での検索

これらは、配列で何かを探すためのメソッドです。

indexOf/lastIndexOf and includes

メソッド arr.indexOf, arr.lastIndexOfarr.includes は文字列の場合と同じ構文を持ち、基本的に同じことを行いますが、文字の代わりにアイテムを操作します:

  • arr.indexOf(item, from) はインデックス from から item を探し、見つかった場所のインデックスを返します。そうでない場合は -1 になります。
  • arr.lastIndexOf(item, from) は同じですが、右から左に見ていきます。
  • arr.includes(item, from) はインデックス from から item を探し、見つかった場合、true を返します。

例:

let arr = [1, 0, false];

alert( arr.indexOf(0) ); // 1
alert( arr.indexOf(false) ); // 2
alert( arr.indexOf(null) ); // -1

alert( arr.includes(1) ); // true

メソッドは === 比較を使うことに留意してください。そのため、もしも false を探す場合、ゼロではなく、正確な false を見つけようとします。

もしも含んでいるかをチェックしたいが、正確なインデックスは不要なときは、arr.includes が好ましいです。

また、includes の非常に小さな違いは、indexOf/lastIndexOf と違い、NaN を正しく処理することができます:

const arr = [NaN];
alert( arr.indexOf(NaN) ); // -1 (0 になるべきですが, === 等値は NaN では機能しません)
alert( arr.includes(NaN) );// true (正しい)

find と findIndex

オブジェクトの配列を持っていることを想像してください。特定条件を持つオブジェクトをどのようにして見つけますか?

ここで arr.find メソッドが便利です。

構文はこうです:

let result = arr.find(function(item, index, array) {
  // true が返却されると、item が返却され、イテレーションは停止します
  // 偽の場合は undefined です
});

関数は配列の要素毎に繰り返し呼ばれます:

  • item は要素です。
  • index はインデックスです。
  • array は配列自身です。

もし true を返すと、検索が止まり、item が返却されます。見つからない場合は undefined になります。

例えば、ユーザの配列を持っており、それぞれフィールド idname を持っているとします。id == 1 のものを見つけましょう:

let users = [
  {id: 1, name: "John"},
  {id: 2, name: "Pete"},
  {id: 3, name: "Mary"}
];

let user = users.find(item => item.id == 1);

alert(user.name); // John

現実の世界では、オブジェクトの配列は一般的なことです。なので、 find メソッドは非常に役立ちます。

例の中では1つの引数、関数 item => item.id == 1find を行っている点に注意してください。find の他のパラメータは殆ど使われません。

arr.findIndex メソッドは基本的に同じです。が、要素自体ではなく要素が見つかったインデックスを返します。

filter

find メソッドは、関数が true を返すようにする単一の(最初の)要素を探します。

もし複数になる可能性がある場合、arr.filter(fn) を使います。

構文は大体 find と同じですが、filter はマッチしたすべて要素の配列を返します:

let results = arr.filter(function(item, index, array) {
  // true の場合、item は results にプッシュされ、イテレーションは継続します
  // 何も見つからない場合は、空配列を返します
});

例:

let users = [
  {id: 1, name: "John"},
  {id: 2, name: "Pete"},
  {id: 3, name: "Mary"}
];

// 最初の2人のユーザの配列を返します
let someUsers = users.filter(item => item.id < 3);

alert(someUsers.length); // 2

配列を変換する

このセクションでは、配列を変換または並び替える方法について説明します。

map

arr.map メソッドは最も便利なものの1つで、よく使われます。

これは、配列の各要素に対して関数を呼び出し、結果の配列を返します。

構文は次の通りです:

let result = arr.map(function(item, index, array) {
  // item の代わりに新しい値を返します
})

例えば、ここでは各要素をその長さに変換します:

let lengths = ["Bilbo", "Gandalf", "Nazgul"].map(item => item.length)
alert(lengths); // 5,7,6

sort(fn)

メソッド arr.sort は配列を 決まった位置に ソートし、要素の順番を変更します。

これもソートされた配列を返しますが、arr 自身が変更されるので、返却値は通常無視されます。

例:

let arr = [ 1, 2, 15 ];

// このメソッドは arr の内容を並べ替え(てそれを返します)
arr.sort();

alert( arr );  // 1, 15, 2

出力結果が何か不自然であることに気づきましたか?

並び順が 1, 15, 2 となりました。正しくないようです。しかしなぜでしょう?

アイテムは、デフォルトでは文字列としてソートされます。

文字通り、すべての要素は文字列に変換され、比較されます。なので、辞書編集順序が適用され、実際には "2" > "15" となります。

私たち独自のソート順を使うためには、arr.sort() の引数として、2つの引数をもつ関数を指定する必要があります。

関数はこのように動作する必要があります:

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

例:

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

let arr = [ 1, 2, 15 ];

arr.sort(compareNumeric);

alert(arr);  // 1, 2, 15

これで意図したとおりに動作します。

立ち止まって何が起きているのか考えてみましょう。arr は何でも配列にすることができます。それは数値や文字列、html要素やその他何でも含まれる可能性があります。私たちは 何かの セットを持っています。それらをソートするためには、要素を比較する方法を知っている 順序付け関数 が必要です。 デフォルトは文字列です。

arr.sort(fn) メソッドは組み込みでソートアルゴリズムの実装を持っています。私たちはそれが正確にどのように動作するかについては気にする必要はありません (殆どの場合、最適化されたクイックソート です)。配列の要素を見ていき、提供された関数を使ってその要素を比較し、並べ替えます。私たちに必要なのは、比較を行う fn を提供することだけです。

ところで、もしどの要素が比較されているかを知りたいとき、alert をしても問題ありません:

[1, -2, 15, 2, 0, 8].sort(function(a, b) {
  alert( a + " <> " + b );
  return a - b;
});

アルゴリズムは処理の中で複数回要素を比較しますが、できるだけ回数を少なくしようとします。

比較関数は任意の数を返すことがあります

実際には、比較関数は正の数を「より大きい」、負の数を「より小さい」として返せば十分です。

より短い関数で書くことができます:

let arr = [ 1, 2, 15 ];

arr.sort(function(a, b) { return a - b; });

alert(arr);  // 1, 2, 15
ベストなアロー関数

アロー関数 を覚えていますか? すっきりしたソートを書くために使えます。:

arr.sort( (a, b) => a - b );

これは、他の上で書いているより長いバージョンとまったく同じように動作します。

文字列には localeCompare を使用します

stringsの比較アルゴリズムを思い出してください。デフォルトではコードで文字比較を行います。

多くのアルファベットでは、Ö などの文字のソートを正しく行うためのメソッド str.localeCompare を使用するのがよいです。

例えば、ドイツ語でいくつかの国をソートしてみましょう:

let countries = ['Österreich', 'Andorra', 'Vietnam'];

alert( countries.sort( (a, b) => a > b ? 1 : -1) ); // Andorra, Vietnam, Österreich (間違い)

alert( countries.sort( (a, b) => a.localeCompare(b) ) ); // Andorra,Österreich,Vietnam (正しい!)

reverse

メソッド arr.reversearr 内の要素の順序を逆転させます。

例:

let arr = [1, 2, 3, 4, 5];
arr.reverse();

alert( arr ); // 5,4,3,2,1

また、反転後に配列 arr を返します。

split と join

ここでは現実世界でのシチュエーションを考えます。私たちはメッセージングアプリを書いており、利用者はカンマ区切りで受信者のリスト(John, Pete, Mary)を入力します。しかし、我々にとっては、1つの文字列よりも名前の配列の方がはるかに扱いやすいです。それを得る方法は?

str.split(delim) メソッドは、まさにそれをします。与えられた区切り文字 delim で文字列を配列に分割します。

下の例では、スペースに続くカンマで分割しています:

let names = 'Bilbo, Gandalf, Nazgul';

let arr = names.split(', ');

for (let name of arr) {
  alert( `A message to ${name}.` ); // A message to Bilbo  (と他の名前)
}

split メソッドは任意の2つ目の数値の引数を持っています。それは配列の長さの制限です。もしこれが指定された場合、余分な要素は無視されます。実際にはほとんど使われませんが。:

let arr = 'Bilbo, Gandalf, Nazgul, Saruman'.split(', ', 2);

alert(arr); // Bilbo, Gandalf
文字への分割

s を空にして split(s) を呼び出すと、文字列を文字の配列に分割します:

let str = "test";

alert( str.split('') ); // t,e,s,t

arr.join(str)split と逆を行います。arr のアイテムを str で繋いだ文字列を作ります。

例:

let arr = ['Bilbo', 'Gandalf', 'Nazgul'];

let str = arr.join(';');

alert( str ); // Bilbo;Gandalf;Nazgul

reduce/reduceRight

配列に対して繰り返し処理が必要なときは、forEach, for あるいは for..of を使うことができます。

各要素のデータを反復して返す必要があるときには、mapを使うことができます。

メソッド arr.reducearr.reduceRight もまたその種類に属しますが、少し複雑です。それらは、配列に基づいて単一の値を計算するために使用されます。

構文:

let value = arr.reduce(function(accumulator, item, index, arr) {
  // ...
}, initial);

関数は各要素に順番に適用され、その結果を次の呼び出しに “引き継ぎ” ます:

引数:

  • accumulator – 前の関数呼び出しの結果で、初回は initial と等価です(initial が指定されている場合)
  • item – 現在の配列の項目です。
  • index – 位置です。
  • arr – 配列です。

関数が適用されると、前の関数呼び出しの結果が、次の関数呼び出しの最初の引数として渡されます。

したがって、最初の引数は基本的に、以前のすべての実行の結合結果を格納するアキュムレータです。そして、最後にそれは reduce の結果になります。

複雑に見えますか?

これを掴むための最も簡単な方法は、例を見る、です。

ここでは、1行で配列の合計を取得します。

let arr = [1, 2, 3, 4, 5];

let result = arr.reduce((sum, current) => sum + current, 0);

alert(result); // 15

ここでは、2つの引数だけを使用する reduce の最も一般的なパターンを使用しました。

何が起きているか、詳細を見てみましょう。

  1. 最初の実行で suminitial 値(reduce の最後の引数)であり、 0 です。そして、current は最初の配列要素で 1 になります。従って、結果は 1 です。
  2. 2回目の実行では、sum = 1 で、2つ目の配列要素(2)をそれに足して返します。
  3. 3回目の実行では、sum = 3 で、それに1つ要素を足します。それが続きます。

計算のフロー:

また、次のテーブルでは、各行は次の配列要素の関数呼び出しを表しています。

sum current result
最初の呼び出し 0 1 1
2回目の呼び出し 1 2 3
3回目の呼び出し 3 3 6
4回目の呼び出し 6 4 10
5回目の呼び出し 10 5 15

これらから分かるように、前の呼び出しの結果は次の実行のときの最初の引数になっています。

また、 initial 値を省略することもできます。:

let arr = [1, 2, 3, 4, 5];

// reduce から初期値を削除する(最後の 0 を削除)
let result = arr.reduce((sum, current) => sum + current);

alert( result ); // 15

結果は同じです。なぜなら、初期値が指定されていない場合、reduce は配列の最初の要素を初期値とみなし、2つ目の要素から繰り返し処理を始めるためです。

計算テーブルは、上と同じで最初の行を引いたものです。

しかし、このような利用は極度の注意を要求します。もし配列が空の場合、初期値がない状態で reduce を呼び出すと、エラーが発生してしまいます。

例: