JavaScriptの配列には型はないため、配列の要素にはどの型の値でも格納できる。つまり、配列の要素にオブジェクトやほかの配列を格納することも可能。

JavaScirptの配列のインデックスは0から始まり、32ビットの整数値になる。つまり最大のインデックス値は4294967294(2^32-2)となる つまり最大4294967295個の要素を持つ配列を生成することができる。 また、処理時に数値インデックスを文字列に変換、その後この文字列をプロパティ名として使用する。

JavaScriptの配列は動的である 配列の生成時に配列の長さを湖底で指定する必要がないかつ長さを変更するときに再割り当てする必要もない また、配列は疎(つまり最初から最後まで連続して要素が格納されていない)でも問題ない

配列はすべてlengthプロパティを持ち、疎ではない配列の場合、lengthは単純に配列の要素数を表す一方、疎な配列の場合、lengthは格納されていない要素を含めた配列の要素数を表す

また配列はArray.prototypeからプロパティを継承し、このプロトタイプには配列を操作するためのメソッドが用意されている これらのメソッドの大半は汎用的で、配列以外の配列の様なオブジェクト(§7.9参照)にも適用できる 文字列は実は文字の配列のようなふるまいをする(§7.10参照)

ES2015では、「型付き配列」という新たな配列クラスが追加された 通常の配列とは異なり型付き配列は固定の長さを持ち、要素は固定の数値型になる 型付き配列を使うとバイナリデータを高速に処理したり、バイト単位でアクセスできるようになる 型付き配列は§11.2参照

配列は特殊なオブジェクトであり両者には共通点がある 例えば配列の要素にアクセスする[]演算子はオブジェクトのプロパティにアクセスする[]演算子と同じように動作する ただし、配列とオブジェクトは共通部分もあるが異なる点もある 配列のすべてのインデックスはプロパティ名になるが、前述のとおり0から4294967294までの整数のプロパティのみがインデックスである ただしすべての配列はオブジェクトなので配列に対して任意の名前のプロパティを作成できる その場合は配列は特殊な動きをし、必要に応じてlengthプロパティの値を変更する

負数や整数ではない数値を使って配列のインデックスを作成することもできる 数値は文字列に変換され、この文字列がプロパティ名として使われる この場合、プロパティ名は非負数の整数ではないため、配列のインデックスとしてではなく、通常のオブジェクトのプロパティとして扱われる 逆に意図せず非負数の整数で配列のインデックスを作成した場合、オブジェクトのプロパティではなく通常の配列のインデックスを作成することになる(整数値になる浮動小数点値も同様) §7.2にて具体例を挙げる

最後に、配列のインデックスはオブジェクトの特殊なプロパティなので、「配列の範囲外」のエラーは発生しない オブジェクトの存在しないプロパティを取得使用としたときにundefinedが返るのと同じように、配列の存在しないインデックスを取得しようとするとundefinedが返る

7.1 配列の生成

配列を生成するには、配列リテラルを使う方法、反復可能なオブジェクトに対するスプレッド演算子を使う方法、 Array()コンストラクタを使う方法、Array.of()ファクトリメソッドと、Array.from()ファクトリメソッドを使う方法がある

7.1.1 配列リテラル

配列を生成するには[]の中に配列の要素をカンマで区切る配列リテラルを使うのが最も感嘆 配列リテラルには定数だけでなく、任意の式やオブジェクトリテラル、ほかの配列リテラルも記述可能である また、配列リテラルの中に値を記述せずカンマを連続で記述すると配列は疎になる つまり、値を指定しなかった場合、配列の要素は存在せず値を読み出すとundefinedが返る

let empty = []; // 要素がない配列
let primes = [2, 3, 5, 7, 11]; // 5つの数値要素を持つ配列
let misc = [1.1, true, 'a']; // 3つの様々な型の要素を持つ配列、最後のカンマは無視される

let base = 1024;
let table = [base, base + 1, base + 2, base + 3]; // 配列リテラルの中に式を記述することも可能

let b = [
  [1, { x: 1, y: 2 }],
  [2, { x: 3, y: 4 }],
]; // 配列リテラルの中にオブジェクトリテラルやほかの配列リテラルを記述することも可能

let count = [1, , 3]; // インデックス1の要素が存在しない疎な配列
let undefs = [, ,]; // 3つの要素が存在しない疎な配列 ただし末尾のカンマが無視されるためlengthの値は2になる

7.1.2 スプレッド演算子

ES2015以降はスプレッド演算子を使って配列リテラルの中にほかの配列の要素を含めることができる 配列の浅いコピーを作るのに便利

スプレッド演算子は反復可能なオブジェクトに対して機能する(つまりfor/ofループで反復可能なもの 詳細は§12参照) 例えば文字列は反復可能なのでスプレッド演算子を使えば任意の文字列をその文字列の文字を1文字ずつ要素に持つ配列に変換できる Setオブジェクトも反復可能なので配列から重複する要素を削除するには配列をセットに変換しその後スプレッド演算子で配列に戻すのが簡単

let a = [1, 2, 3];
let b1 = [0, ...a, 4]; // b1 = [0, 1, 2, 3, 4]
b1[1] = 0; // b1 = [0, 0, 2, 3, 4] aは変更されない
a[1]; // 2

let digits = [...'0123456789 ABCDEFG'];
digits; // ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", " ", "A", "B", "C", "D", "E", "F", "G"]

7.1.3 Array()コンストラクタ

Array()コンストラクタで呼び出す方法には3種類ある

  • 引数無しで呼び出す方法:要素のない空の配列が生成される
  • 数値を1つだけ引数にして呼び出す方法:数値は配列の長さになり、指定した長さの要素が空な配列が生成される 0や1などの配列インデックスのプロパティも定義されていない
  • 配列に対して、明示的に2つ以上の配列要素、または数値以外の要素を1つだけ指定して呼び出す方法:コンストラクタに対する引数を要素として持つ配列が生成される この場合だと配列リテラルのほうが簡単
let a1 = Array(); // 空の配列
let a2 = Array(10); // 長さ10の配列、要素はすべてundefined
let a3 = Array(5, 4, 3, 2, 1, 'testing, testing'); // [5, 4, 3, 2, 1, "testing, testing"]の要素を持つ配列

7.1.4 Array.of()

Array()コンストラクタでは数値を1つだけ引数にして呼び出すと、その数値は配列の長さになってしまい、数値1つだけの配列を生成することができない ES2015以降Array.of()関数を使うことで、引数の数には関係なく、引数を要素として持つ配列を生成することができる

let a4 = Array.of(); // 空の配列
let a5 = Array.of(10); // [10]の要素を持つ配列
let a6 = Array.of(1, 2, 3); // [1, 2, 3]の要素を持つ配列

7.1.5 Array.from()

ES2015にはもう一つ配列を生成するためのファクトリメソッドArray.from()がある 第1引数として反復可能オブジェクトか配列のようなオブジェクトを指定すると、そのオブジェクトの要素を持つ配列を生成する 第2引数には省略可能であるが関数を指定でき、第1引数の各要素に対してその関数を適用する これは配列のmap()メソッドと同じような働きをするが、配列が生成されるときにマッピングを行った方が配列を作成した後に新しい配列にマッピングするよりも効率的である

配列のようなオブジェクトとは数値を持つlengthプロパティを持ち、プロパティ名が整数値である非配列オブジェクトのこと

7.2 配列の要素の読み書き

配列の要素にアクセスするときには[]演算子を使う 負ではない整数値を持つ任意の式を[]の中に書いて配列の要素の読み書きが出来る プロパティ名として0以上4294967295(2^32-1)未満の整数値を使った場合は、配列は自動的にlengthプロパティの値を変更する

let a7 = ['world']; // 要素が1つの配列
let value = a7[0]; // "world" a7の要素0を読み出す
a7[1] = 3.14; // a7の要素1に3.14を代入する
let i = 2;
a7[i] = 3; // a7の要素2に3を代入する
a7[i + 1] = 'hello'; // a7の要素3に"hello"を代入する
a7[a7[i]] = a7[0]; // a7の要素3にa7の要素0を代入する
a7; // ["world", 3.14, 3, "world", "hello"]

let o1 = {}; // 空のオブジェクト
o1[1] = 'one'; // 要素1に"one"を代入する
o1['1']; // "one" 要素1と"1"は同じ要素を指す 配列は数値インデックスを文字列に変換してプロパティ名として使うため

a7[-1.23] = true; // -1.23という名前のプロパティを作成する
a7['1000'] = 'one thousand'; // 要素1000に"one thousand"を代入する
a7[1.0]; // 3.14 要素1と1.000は同じ要素を指す

let a8 = [true, false];
a8[2]; // undefined a8は要素2を持たない
a[-1]; // undefined この名前のプロパティはない

7.3 疎な配列

疎な配列とは度々言及している通り要素が0から順に連続して埋まっていない配列のこと 通常配列のlengthプロパティは配列中の要素数を表すが配列が疎の場合、lengthプロパティの値は要素数よりも大きな値になる

以下の方法で疎な配列を生成することができる

  • 配列リテラルの中で要素を省略する
  • 配列の長さを指定して作成 Array()コンストラクタを使い、配列の長さだけを指定すると指定された長さの疎な配列が生成される
  • 配列のインデックスを直接指定して部分的に要素を埋める
  • delete演算子を使って配列の特定の要素を削除する(§7.5参照)

疎な配列は密な配列よりも実行速度が遅い代わりにメモリ効率が良くなる また、疎な配列では要素を探す時間は通常のオブジェクトプロパティを探すのと同じくらいである

let sparseArray = [1, , 3]; // 要素を省略した疎な配列

let arr = new Array(3); // 長さ3の疎な配列を新しく生成する
arr[1] = 2; // 要素1に2を代入する
arr; // [undefined, 2, undefined] 要素0と2がundefinedの疎の配列

let arr1 = [1, 2, 3]; // 3つの要素を持つ配列
delete arr1[1]; // 要素1を削除する
arr1; // [1, undefined, 3] 要素1がundefinedの疎な配列

疎な配列はundefinedの値を持つ通常の配列と扱っても問題ない

7.4 配列の長さ

すべての配列はlengthプロパティを持つ 配列が通常のオブジェクトと異なっている点の一つである 密な配列の場合、lengthプロパティは配列の要素数を表すが、疎な配列の場合、lengthプロパティは配列の要素数ではなく未定義の要素を含めた配列の要素数を表す lengthプロパティは動作を保つため、以下の2つの特殊なふるまいをする

  • lengthプロパティの値を変更すると配列の要素数が変わる

    配列の現在のlength以上のインデックスiを指定して配列の要素に値を設定した場合、lengthプロパティの値はi+1に変更される

  • lengthプロパティに現在の値より小さい非負数の整数nを設定した場合、n以上のインデックスを持つ配列の要素は配列から削除される

let a9 = []; // 空の配列
a9.length[('a', 'b', 'c')].length; // 0 空の配列のlengthプロパティは0 // 3 3つの要素を持つ配列のlengthプロパティは3

a10 = [1, 2, 3, 4, 5]; // 5つの要素を持つ配列
a10.length = 3; // a10の要素数が3になる a10 = [1, 2, 3]
a10.length = 0; // a10の要素数が0になる a10 = []
a10.length = 5; // a10の要素数が5になる a10 = [undefined, undefined, undefined, undefined, undefined] new Array(5)と同じ

7.5 配列の要素の追加と削除

配列に要素を追加するには下記の方法がある

  • 配列の新しいインデックスに値を設定する
  • push()メソッドを使って配列の末尾に要素を追加する(aという配列があった場合、a[a.length]と同じ §7.8参照)
  • unshift()メソッドを使って配列の先頭に要素を追加する(§7.8参照)

配列の要素を削除するには下記の方法がある

  • delete演算子を使って配列の特定の要素を削除する
  • pop()メソッドを使って配列の末尾の要素を削除する(§7.8参照)
  • shift()メソッドを使って配列の先頭の要素を削除する(§7.8参照)

他に汎用的なメソッドとしてsplice()メソッドがある splice()メソッドは配列の任意の位置に要素を追加したり削除したり置換したりすることができる splice()メソッドを使うと、lengthプロパティは変更され、必要に応じて配列要素は前後にシフトされる 詳細は§7.8参照

let a11 = []; // 空の配列
a11[0] = 1; // 要素0に1を代入する
a11[1] = 2; // 要素1に2を代入する
a11.push(3); // a11の末尾に3を追加する
a11.push(4, 5); // a11の末尾に4と5を追加する a11 = [1, 2, 3, 4, 5]

delete a11[1]; // a11の要素1を削除する
2 in a11; // false a11の要素1は存在しない
a11.length; // 5 delete演算子は要素を削除するだけでlengthプロパティの値は変更しない

7.6 配列の要素の巡回

ES2015以降は配列(または反復可能なオブジェクト)の各要素をループしたい場合はfor/ofループを使うのが簡単 for/ofについては§5.4.4参照 for/ofループしている際に配列の各要素のインデックスを知りたい場合はentries()メソッドを使う

配列に対して反復処理をする場合forEach()メソッドを使うこともできる これは関数型の手法を提供するメソッドで、配列のforEach()メソッドに対して関数を渡すと、配列の各要素に対してforEach()に渡した関数が呼び出される 詳細は§7.8.1参照 同様にmap()やfilter()メソッドなどもあるがこれらも§7.8.1参照

let letters = [...'Hello world'];
let string = '';
for (let letter of letters) {
  string += letter;
}
string; // "Hello world"

let everyother = '';
for (let [index, letter] of letters.entries()) {
  if (index % 2 === 0) everyother += letter; // 偶数インデックスの文字だけを取得する
}
everyother; // "Hlowrd"

let vowels = '';
for (let i = 0; i < letters.length; i++) {
  let letter = letters[i];
  if (/[aeiou]/.test(letter)) {
    // 正規表現を使って母音を判定する
    vowels += letter;
  }
}
vowels; // "eoo"

7.7 多次元配列

JavaScriptでは本当の意味での多次元配列はサポートしていないものの、配列の要素に配列を格納することで多次元配列のようなふるまいを実現することができる 配列の配列要素には[]演算子を2回使えばアクセスできる

let table1 = new Array(10); // 長さ10の配列
for (let i = 0; i < table1.length; i++) {
  table1[i] = new Array(10); // 長さ10の配列を生成
}

7.8 配列のメソッド

配列クラスで定義されているメソッド群について説明する これらのメソッドはメソッドを呼び出した配列そのものを変更するものもあれば、呼び出した配列は変更しないものもある また多くのメソッドは配列を返し、元の配列は変更せず新しい配列を返す場合もあれば、元の配列を変更して元の配列を返す場合もある

配列のメソッドはおおまかに以下のようなものに分類できる

  • イテレータメソッド:配列の各要素に対して関数を適用するメソッド
  • スタック/キューメソッド:配列の先頭や末尾に要素を追加したり削除したりするメソッド
  • サブ配列メソッド:配列の一部を取り出す、置換する、削除する、埋める、コピーするメソッド
  • 検索(ソート)メソッド:配列の要素を検索したりソートするメソッド

またArrayクラスの静的メソッドや、そのほか配列を結合したり文字列に変換するメソッドについても説明する

7.8.1 配列イテレータメソッド

配列イテレータメソッドとは配列をループして配列のヨス尾を順番に指定した関数に渡すメソッドのこと 配列の反復処理やマッピング、フィルタリング、テスト、集約を行うときに便利なメソッド

この項で説明するメソッドはすべて第1引数として関数を受け取る この関数は要素ごとまたはいくつかの要素に対して一度ずつ呼び出される 配列がその場合は存在しない要素に対しては関数は呼び出されない 第1引数の関数には多くの場合、配列要素の値とインデックス、配列自身の3つの引数が渡されることが多い 必要なのは配列要素の値だけのことが多いのでその場合は第2引数と第3引数は省略することができる

第2引数は省略可能で、第1引数の関数は第2引数のメソッドとして呼び出される つまり第1引数の関数内のthisは第2引数の値になる

第1引数に指定する関数はほかの場所で定義したものではなく、メソッドを呼び出す式の中でインラインで定義するのが一般的である この時アロー関数(§8.1.3参照)を使うと便利

7.8.1.1 forEach()メソッド

forEach()メソッドは配列の各要素に対して関数を呼び出すメソッドである 前述のとおり、第1引数に関数を指定する その関数を呼び出す際は引数を3つ(要素の値、インデックス、配列自身)渡すことができる 要素の値しか必要でない場合は引数を1つだけ受け取るような関数を既述すればよい

forEach()メソッドにはすべての要素が関数に渡されるまで実行を中止する方法はないので注意

let data = [1, 2, 3, 4, 5],
  sum = 0;
// 配列の要素の総計を計算する
data.forEach((value) => {
  sum += value;
}); // sum = 15

// 配列の各要素をインクリメントする
data.forEach((v, i, a) => {
  a[i] = v + 1;
}); // data = [2, 3, 4, 5, 6]

7.8.1.2 map()メソッド Non-Destructive Method

map()メソッドは配列の要素を1つずつ指定した関数に引数として渡し、この関数から返された値を配列に格納しその配列を返す map()メソッドとforEach()メソッドの違いはmap()メソッドは新しい配列を返し、元の配列は変更しない点である そのため、map()メソッドの場合は指定した関数から値を返すようにする必要がある 配列が疎の場合、存在しない要素に対しては関数は呼び出されず、map()メソッドから返される配列も元の配列通り疎になる つまり元の配列と長さが同じになり、同じインデックスの要素が存在しない配列が返される

let a12 = [1, 2, 3];
let b2 = a12.map((x) => x * x); // b2 = [1, 4, 9]

7.8.1.3 filter()メソッド Non-Destructive Method

filter()メソッドは、配列の要素の部分集合となる配列を返す filter()メソッドに指定した関数が述語(predicate)関数になる この述語関数の戻り値がtrueの場合、その要素は返される配列に含まれる filter()メソッドは元の配列を変更せず、新しい配列を返す 疎の配列の場合、存在しない要素をスキップするため必ず密な配列が返される

let a13 = [5, 4, 3, 2, 1];
let smallvalues = a13.filter((x) => x < 3); // smallvalues = [2, 1]: 3未満の要素を取得する
let everyother1 = a13.filter((x, i) => i % 2 === 0); // everyother1 = [5, 3, 1]: 奇数インデックスの要素を取得する

// 疎な配列を密な配列に変換する例
let sparse = [1, , 3, , 5];
let dense = sparse.filter(() => true); // dense = [1, 3, 5]

// undefinedやnullを持つ要素を取り除きたい場合の例
a = a.filter((x) => x !== undefined && x !== null);

7.8.1.4 find()メソッドとfindIndex()メソッド Non-Destructive Method

find()メソッドとfindIndex()メソッドはfileter()メソッドと同じく指定した述語関数がtrueを返す要素を探す ただし、find()メソッドとfindIndex()メソッドは最初に見つかった要素のみを返し、処理を中断する 処理を中断した際にfind()メソッドは見つけた要素を返し、findIndex()メソッドは見つけた要素のインデックスを返す 要素が見つからなかった場合はfind()メソッドはundefinedを返し、findIndex()メソッドは-1を返す

let a14 = [1, 2, 3, 4, 5];
let even = a14.find((x) => x % 2 === 0); // even = 2: 最初に見つかった偶数の要素を取得する
a14.find((x) => x % 7 === 0); // undefined: 7の倍数の要素はない
let evenIndex = a14.findIndex((x) => x % 2 === 0); // evenIndex = 1: 最初に見つかった偶数の要素のインデックスを取得する
a14.findIndex((x) => x % 7 === 0); // -1: 7の倍数の要素はない

7.8.1.5 every()メソッドとsome()メソッド Non-Destructive Method

every()メソッドとsome()メソッドは配列に対して述語関数を適用しtrueかfalseを返す every()メソッドは記号論理学における全称量化子(∀)に相当し、すべての要素に対して指定した述語関数がtrueを返した場合にtrueを返す some()メソッドは存在量化子(∃)に相当し、配列の少なくとも一つの要素に対して述語関数がtrueを返した場合にtrueを返す また、some()メソッドはすべての要素に対して述語関数がfalseを返した場合にのみfalseを返す

every()メソッドもsome()メソッドも戻り値が確定した時点で処理を中断する 例えばsome()メソッドはtrueを初めて返したときに中断しtrueを返し、falseを返し続けた場合にのみ処理を続ける

数学上の定義と同じように空の配列に対してはevery()メソッドはtrueを返し、some()メソッドはfalseを返す

let a15 = [1, 2, 3, 4, 5];
a15.every((x) => x < 10); // true: すべての要素が10未満である
a15.every((x) => x % 2 === 0); // false: すべての要素が偶数ではないため

a15.some((x) => x % 2 === 0); // true: 1つ以上の要素が偶数である
a15.some((x) => x > 5); // false: すべての要素が5未満であるため

7.8.1.6 reduce()メソッドとreduceRight()メソッド Non-Destructive Method

reduce()メソッドとreduceRight()メソッドは引数で指定された関数を使い、配列の要素を1つにまとめたものを返す これは関数型プログラミングにおけるinjectやfoldと呼ばれる操作に相当する

reduce()メソッドは配列の先頭から要素を処理し、reduceRight()メソッドは配列の末尾から要素を処理する

reduce()メソッドの第1引数には各要素を処理するための関数、第2引数は省略可能で、第1引数の関数における累積値の初期値を指定する 第2引数の初期値が指定されなかった場合、初期値は配列の最初の要素になり、処理は2番目の要素から始まる 第1引数の関数には4つの引数が渡される

  1. 累積値(accumulator): 現在の累積値 計算の結果を保持する変数
  2. 現在の値(current value): 現在処理している配列の要素
  3. 現在のインデックス(current index): 現在処理している配列の要素のインデックス(省略可能)
  4. 配列全体(array): reduce()メソッドを呼び出した元の配列(省略可能)

reduce()メソッドもreduceRight()メソッドも簡約化関数が呼び出されるときのthisの値を指定できない 簡約化関数をある特定のオブジェクトのメソッドとして呼び出したい場合はFunction.bind()メソッドを使う(8.7.5参照)

let a16 = [1, 2, 3, 4, 5];
let sum1 = a16.reduce((x, y) => x + y, 0); // sum1 = 15: 0 + 1 + 2 + 3 + 4 + 5
let product = a16.reduce((x, y) => x * y, 1); // product = 120: 1 * 1 * 2 * 3 * 4 * 5
let max = a16.reduce((x, y) => (x > y ? x : y)); // max = 5: 最大値を取得する

// reduceRight()メソッドの例
let a17 = [2, 3, 4];
let pow = a17.reduceRight((acc, val) => Math.pow(val, acc)); // pow = 2.4178516392292583e+24: 2^(3^4) まずaccに4、valに3が渡される 結果の81がaccに渡される...

7.8.2 flat()メソッドとflatMap()メソッドによる配列のフラット化 Non-Destructive Method

ES2019よりflat()メソッドが追加された flat()メソッドは、このメソッドを呼び出した配列と同じ要素を持つ新しい配列を作成し、その配列を返す 新しい配列を作成する際、元の配列の各要素をフラット化(展開)されて返す

引数を指定しなかった場合、flat()は入れ子の1階層分だけ展開する つまり元の配列の要素のうち、その要素自身が配列の場合は展開されるが、その配列の要素が配列の場合は展開されない 展開する階層を変更したい場合、flat()メソッドの引数を指定することで展開する階層を指定できる

flatMap()メソッドはmap()メソッドとflat()メソッドを組み合わせたものである ただし、返される配列が自動的に展開される点が異なる a.flatMap(f)はa.map(f).flat()と同じ結果を返すがflatMap()メソッドの方が効率的である flatMap()は、map()メソッドを汎化して入力配列の各要素に対して出力配列の任意の数の要素にマップできるようにしたものと考えてもよい 他にはflatMap()を使えば入力要素を空の配列にマップすることもできる

let a18 = [1, [2, [3, 4]]];
let b3 = a18.flat(); // b3 = [1, 2, [3, 4]] 入れ子の1階層分だけ展開される
let c = a18.flat(2); // c = [1, 2, 3, 4] 入れ子の2階層分展開される
let d = a18.flat(4); // d = [1, 2, 3, 4] 入れ子の4階層分展開される この場合は入れ子の最大階層数を超えているため、すべての入れ子が展開される

let phrases = ['hello world', 'the quick brown fox'];
let words = phrases.flatMap((phrase) => phrase.split(' ')); // words = ["hello", "world", "the", "quick", "brown", "fox"]
[-2, -1, 1, 2].flatMap((x) => (x < 0 ? [] : Math.sqrt(x))); // [1, 1.4142135623730951]

7.8.3 concat()メソッドによる配列の結合 Non-Destructive Method

concat()メソッドは、concat()メソッドを呼び出した配列の要素の後ろに、引数で指定された配列の要素を追加した新しい配列を生成して返す 引数のうちいずれかが配列である場合、配列のまま追加するのではなく、配列の要素を展開して配列に追加する(入れ子の配列は展開されない) ただしconcat()メソッドは配列の配列を再帰的に展開することはない concat()は場合によって負荷が大きい処理のため、元の配列を操作するpush()やsplice()メソッドを使うことも候補にいれるとよい

let a19 = [1, 2, 3];
a19.concat(4, 5); // [1, 2, 3, 4, 5]
a19.concat([4, 5], [6, 7]); // [1, 2, 3, 4, 5, 6, 7]
a19.concat(4, [5, [6, 7]]); // [1, 2, 3, 4, 5, [6, 7]]: 入れ子になった配列は展開されない
a19; // [1, 2, 3]: 元の配列は変更されない

7.8.4 push()メソッド、pop()メソッド、shift()メソッド、unshift()メソッドによるスタック/キュー Destructive Method

push()メソッドとpop()メソッドを使用するとスタックと同じやり方で配列を操作できる push()メソッドは配列の最後に1個または複数の要素を追加し、追加した後の配列の長さを返す push()メソッドでは引数に配列を指定しても配列を展開せず、その配列を要素として追加する pop()メソッドは配列の最後の要素を削除してその削除した要素を返す

unshift()メソッドとshift()メソッドを使用すると配列の先頭の位置で要素の出し入れをする unshift()メソッドは配列の先頭に1個または複数の要素を追加し、既存の配列要素をより大きいインデックス番号の方向へシフトさせた後、追加した後の配列の長さを返す shift()メソッドは配列の先頭の要素を削除して空になった場所へ後続の要素をずらし、その削除した要素を返す

unshift()とshift()を使ってスタックを実装することは出来るが、push()とpop()を使う方が効率的である(シフト操作が入るため) ただし、push()とshift()でキュー構造を作ることはできる

let stack = [];
stack.push(1, 2); // stack = [1, 2]
stack.pop(); // 2 stack = [1]
stack.push(3); // stack = [1, 3]
stack.pop(); // 3 stack = [1]
stack.push([4, 5]); // stack = [1, [4, 5]]
stack.pop(); // [4, 5] stack = [1]
stack.pop(); // 1 stack = []

let queue = [];
queue.push(1, 2); // queue = [1, 2]
queue.shift(); // 1 queue = [2]
queue.push(3); // queue = [2, 3]
queue.shift(); // 2 queue = [3]
queue.shift(); // 3 queue = []

let a20 = [1, 2, 3];
a20.unshift(4, 5); // a20 = [4, 5, 1, 2, 3]
a20.shift(); // 4 a20 = [5, 1, 2, 3]
a20.unshift([4, 5]); // a20 = [[4, 5], 5, 1, 2, 3]
a20.shift(); // [4, 5] a20 = [5, 1, 2, 3]

7.8.5 slice()メソッド、splice()メソッド、fill()メソッド、copyWithin()メソッドによるサブ配列操作

配列には連続する領域や配列の「サブ配列(スライス)」を処理するメソッドが数多く定義されている ここではスライスの抽出、置換、要素の設定、コピーを行うメソッドについて説明する

7.8.5.1 slice()メソッド Non-Destructive Method

slice()メソッドは指定された配列のサブ配列(スライス)を返す 返すスライス配列の先頭を第1引数で指定し、第2引数でスライス配列の末尾を指定する(第2引数は省略可能) スライス配列には第1引数で指定された要素から第2引数で指定された要素の1つ前までの要素が格納される

指定した引数のどちらかが負数の場合、配列の最後からの相対位置で配列要素が指定されたものと解釈される 例えば-1をしてすると配列の最後の要素が指定されたものと解釈される

なおslice()メソッドは元の配列を変更せず、新しい配列を返す

let a21 = [1, 2, 3, 4, 5];
a21.slice(0, 3); // [1, 2, 3]: 0番目から2番目の要素のスライスを取得する
a21.slice(3); // [4, 5]: 3番目から最後の要素のスライスを取得する
a21.slice(1, -1); // [2, 3, 4]: 1番目から最後から2番目の要素のスライスを取得する
a21.slice(-3, -2); // [3]: 最後から3番目から最後から2番目の要素のスライスを取得する

7.8.5.2 splice()メソッド Destructive Method

splice()メソッドは、配列に要素を挿入したり配列から要素を削除したりする汎用的なメソッドであり、削除した要素を返す slice()メソッドやconcat()メソッドとは異なり、splice()メソッドは元の配列を変更する splice()メソッドは要素の挿入や削除を別々に行うだけではなく、挿入と削除を同時に実行することもできる 挿入や削除が行われた後、連続した密の配列になるように要素がシフトされて必要に応じてインデックスが増加/減少する

削除する場合は第1引数に挿入や削除を開始する位置を指定し、第2引数に削除する要素の個数を指定する(第2引数は省略可能) 第2引数を省略した場合は第1引数で指定した位置から最後までの要素が削除される

挿入する場合は第3引数以降が挿入する要素となる 第1引数や第2引数は通常通り削除の動作を行うので、ただ挿入するだけの場合は第2引数に0を指定する

splice()メソッドはconcat()メソッドとは違い、配列要素を展開しないでそのまま挿入する

let a22 = [1, 2, 3, 4, 5, 6, 7, 8];
a22.splice(4); // [5, 6, 7, 8]; a22 = [1, 2, 3, 4]
a22.splice(1, 2); // [2, 3]; a22 = [1, 4]
a22.splice(1); // [4]; a22 = [1]

let a23 = [1, 2, 3, 4, 5];
a23.splice(2, 0, 'a', 'b'); // []; a23 = [1, 2, "a", "b", 3, 4, 5]
a23.splice(2, 2, [1, 2], 3); // ["a", "b"]; a23 = [1, 2, [1, 2], 3, 3, 4, 5]

7.8.5.3 fill()メソッド Destructive Method

fill()メソッドは配列要素または配列のスライスを指定した値に設定する fill()メソッドは元の配列を変更し、変更した配列を返す 第1引数には配列の要素に設定する値を指定する 第2引数は省略可能で、開始位置を指定できる 省略した場合はインデックス0から始まる 第3引数も省略可能で、終了位置を指定できる ただし指定したインデックスの要素は設定されない 省略した場合は配列の長さまでとなる 第2引数と第3引数の値が負数の場合、配列の最後からの相対位置で指定されたものと解釈される

let a24 = new Array(5);
a24.fill(0); // [0, 0, 0, 0, 0]; 配列を0で埋める
a24.fill(9, 1); // [0, 9, 9, 9, 9]; 1番目以降を9に設定する
a24.fill(8, 2, -1); // [0, 9, 8, 8, 9]; インデックス2から最後から2番目を8に設定する

7.8.5.4 copyWithin()メソッド Destructive Method

copyWithin()メソッドは配列のスライスを指定した位置にコピーする copyWithin()メソッドは元の配列を変更し、変更した配列を返す ただし配列の長さは変更しない 第1引数にはコピーする配列のスライスの内の最初の要素のコピー先となるインデックスを指定する 第2引数は省略可能で、コピーを開始するインデックスを指定する 省略した場合はインデックス0から始まる 第3引数も省略可能で、コピーを終了するインデックスを指定する 省略した場合は配列の長さまでとなる 引数として負数を渡した場合は配列の最後からの相対位置で指定されたものと解釈される

copyWithin()メソッドは高速処理が可能で、型付き配列に使うと便利である copyWithin()メソッドはC言語の標準ライブラリのmemmove()関数を元にしている なお、コピー元とコピー先の領域が重なっていてもコピーは問題なく処理される

let a25 = [1, 2, 3, 4, 5];
a25.copyWithin(1); // [1, 1, 2, 3, 4]; 1番目をコピーする
a25.copyWithin(2, 3, 5); // [1, 1, 3, 4, 4]; 3番目から4番目をコピーする 5番目を指定しているが存在しないためコピーされない
a25.copyWithin(0, -2); // [4, 4, 3, 4, 4]; 最後から2番目の4から最後までをコピーする

7.8.6 配列の検索メソッドとソートメソッド

配列には文字列と同じようにindexOf()メソッドやlastIndexOf()メソッド、includes()メソッドが実装されている また、配列の要素を並べ替えるsort()メソッドやreverse()メソッドもある この項では上記メソッドについて説明する

7.8.6.1 indexOf()メソッドとlastIndexOf()メソッド Non-Destructive Method

indexOf()メソッドとlastIndexOf()メソッドは、配列中から指定した値を持つ要素を探し、要素が見つかったら最初に見つかったインデックスを返す 見つからなかった場合-1を返す indexOf()メソッドは配列の先頭から探索を行い、lastIndexOf()メソッドは配列の末尾から探索を行う

両者とも第1引数に探す値を指定する 第2引数は省略可能で、探索を開始するインデックスを指定する 省略した場合はindexOf()メソッドは0、lastIndexOf()メソッドは配列の最後の要素のインデックスから探索を開始する 引数に負数を指定した場合は配列の最後からの相対位置で指定されたものと解釈される

indexOf()メソッドとlastIndexOf()メソッドは引数と配列の要素を比較する際に===演算子を使う そのためNaN値を探すことができない(NaN値は一律で-1を返す) 配列の要素がプリミティブ値ではなくオブジェクトの場合は引数と要素が同じオブジェクトを参照しているかどうかを調べる オブジェクトの内容で比較したい場合はfind()メソッドを使う必要がある

let a26 = [0, 1, 2, 1, 0];
a26.indexOf(1); // 1: 最初から探してインデックス1に1があるため
a26.lastIndexOf(1); // 3: 末尾から探してインデックス3に1があるため
a26.indexOf(3); // -1: 3は存在しないため

// 配列a中に含まれる値xを持つ要素をすべて検索し、その要素のインデックスを持つ配列を返す関数
function findAll(a, x) {
  let results = [],
    len = a.length,
    pos = 0;
  while (pos < len) {
    pos = a.indexOf(x, pos); // pos以降でxを探す
    if (pos === -1) break;
    results.push(pos);
    pos += 1;
  }
  return results;
}

7.8.6.2 includes()メソッド Non-Destructive Method

ES2016よりincludes()メソッドが追加された includes()メソッドは引数を1つ取り、引数で指定した値が配列に含まれている場合はtrueを返し、含まれていない場合はfalseを返す 多くの要素に対して処理を行いたい場合は配列ではなくSetオブジェクトを使うとよい indexOf()メソッドでは===演算子と同じアルゴリズムで比較を行い、NaN値に対してはNaN値を含めてすべて異なると判定するのに対し、 includes()メソッドではNaN値はNaN値と等しいと判断する つまりindexOf()メソッドではNaN値を調べることができないが、includes()メソッドでは調べることができる

let a27 = [1, true, 3, NaN];
a27.includes(true); // true: trueは含まれている
a27.includes(2); // false: 2は含まれていない
a27.includes(NaN); // true: NaNは含まれている
a27.indexOf(NaN); // -1: indexOf()メソッドではNaNは含まれていないと判断される

7.8.6.3 sort()メソッド Destructive Method

sort()メソッドは配列の要素を並び替えするのに使用される sort()メソッドは新しい配列を生成するのではなく既存の配列をソートし、ソートした配列を返す

引数を指定しないでsort()メソッドを呼び出すと、アルファベット順に配列要素をソートする その際、必要に応じて要素を文字列に変換して比較する 配列の要素にundefinedが含まれる場合は、その要素は配列の最後に配置される

アルファベット順以外でソートしたい場合は、sort()メソッドの引数に比較関数を指定する この関数は2つの引数を取り、第1引数を第2引数より前にしたい場合は、関数から返す値をゼロより小さくする必要がある 逆に第1引数を第2引数より後ろにしたい場合は、関数から返す値をゼロより大きくする必要がある 2つの引数が等しい場合(順序関係がない場合)は、関数から返す値はゼロでなければならない

// 数値順にソートする例
let a28 = [33, 103, 3, 726, 200];
a28.sort(function (a, b) {
  return a - b;
}); // [3, 33, 103, 200, 726]
a28.sort((a, b) => a - b); // ラムダ式を使う場合

a28.sort((a, b) => b - a); // [726, 200, 103, 33, 3]: 逆順にソートする

// 大文字と小文字を区別せずにアルファベット順にソートする例
let a29 = ['banana', 'cherry', 'Apple'];
a29.sort(function (s, t) {
  let a = s.toLowerCase();
  let b = t.toLowerCase();
  if (a < b) return -1;
  if (a > b) return 1;
  return 0;
}); // ["Apple", "banana", "cherry"]

7.8.6.4 reverse()メソッド Destructive Method

reverse()メソッドは配列の要素を逆順にするときに使用される reverse()メソッドは新しい配列を生成するのではなく、既存の配列を逆順にし、逆順にした配列を返す

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

7.8.7 配列から文字列への変換 Non-Destructive Method

Arrayクラスには配列を文字列に変換するメソッドが3つ用意されていて、一般的にはログやエラーメッセージを作成するときに使う (配列の内容を後で再利用できるような形式でテキストにしたい場合はJSON.stringify()メソッドを使ってシリアライズするのがよい)

1つめのjoin()メソッドは配列のすべての要素を文字列に変換して連結し、連結した文字列を返す 各要素を区切るための文字を指定することも可能で、特に指定しない場合はカンマが区切り文字として使われる 逆に文字列を分離して配列を生成するメソッドとしてsplit()メソッドがある

2つめのtoString()メソッドはjoin()メソッドの引数を省略した場合と同じように、配列のすべての要素を文字列に変換してカンマで区切り、連結した文字列を返す toString()メソッドはArray.prototype.toString()メソッドのエイリアスである

3つめのtoLocaleString()メソッドはtoString()メソッドと同じように、配列のすべての要素を文字列に変換してカンマで区切り、連結した文字列を返す ただし、各要素を文字列に変換する際に個々の要素を文字列に変換し、それをロケール固有の区切り文字で区切ったリストを出力する 区切り文字が度の文字になるのかは処理系に依存する

let a31 = [1, 'a', new Date()];
a31.join(); // "1,a,Wed Aug 26 2020 21:43:38 GMT+0900 (Japan Standard Time)"
a31.join(' - '); // "1 - a - Wed Aug 26 2020 21:43:38 GMT+0900 (Japan Standard Time)"
a31.join(''); // "1aWed Aug 26 2020 21:43:38 GMT+0900 (Japan Standard Time)"
a31.toLocaleString(); // "1,a,2020/08/26 21:43:38"

let b1 = new Array(10); // 長さ10の空の配列を生成
b1.join('-'); // "---------": 配列の長さが10で要素がすべてundefinedのため、区切り文字で連結される

7.8.8 配列の静的関数

Arrayクラスにはオブジェクトに対して実行するのではなく、Arrayコンストラクタから呼び出す静的関数が3つ定義されている そのうち2つがArray.of()メソッドとArray.from()メソッドでありそれぞれ§7.1.4と§7.1.5で説明した 残りの1つがArray.isArray()メソッドでありこのメソッドは引数に指定した未知の値が配列かどうかを判定する

Array.isArray([]); // true
Array.isArray({}); // false

7.9 配列のようなオブジェクト

以上をまとめるとJavaScriptの配列には通常のオブジェクトとは異なる以下のような特性がある

  • 配列に新しい要素が追加されると自動的にlengthプロパティが更新される
  • lengthプロパティに現在の長さより小さな値を設定することで配列を縮められる
  • 配列はArray.prototypeから便利なメソッドを継承する
  • Array.isArray()は配列に対してtrueを返す

上記に述べた特性は配列固有の特徴だが、ほかにもこの特徴をもつオブジェクトが存在する 例えば通常のオブジェクトに対してlengthプロパティを設定し、対応する非負数の整数値のプロパティを持つようにしたものを配列として扱ってもよい ただしこの配列のようなオブジェクトに対して、通常の配列のメソッドは直接呼び出しが出来ず、またlengthプロパティも通常の配列のような振る舞いをしない

しかし仮に通常の配列に対して配列要素を調べるコードやアルゴリズムがあったとして、それを配列のようなオブジェクトに対しても使用できることが多くある

クライアントJavaScriptではdocument.querySelectorAll()などいくつかのメソッドが配列のようなオブジェクトを返す

let a32 = {};

// 要素を追加して配列のようなオブジェクトを作成する
let j = 0;
while (j < 10) {
  a32[j] = j * j;
  j++;
}
a32.length = j;

// 通常の配列のように配列のようなオブジェクト(a32)を処理する関数
let total = 0;
for (let i = 0; i < a32.length; i++) {
  total += a32[i];
}

// 例えばoが配列のようなオブジェクトであるかどうかを判定する関数を書くとしたら下記になる
// ただし、文字列は配列のようなオブジェクトとして扱われなくなっている(文字列は配列として扱うより文字列として扱った方がよいため)
function isArrayLike(o) {
  if (
    o && // nullやundefinedでない
    typeof o === 'object' && // オブジェクトである
    Number.isFinite(o.length) && // 有限の数値である
    o.length >= 0 && // 非負数である
    Number.isInteger(o.length) && // 整数である
    o.length < 4294967296
  ) {
    // 2^32未満である
    return true;
  } else {
    return false;
  }
}

// 配列のようなオブジェクトはArray.prototypeを継承していないため、配列メソッドを直接呼び出しできないが、Function.call()メソッドを使って呼び出すことができる
let a33 = { 0: 'a', 1: 'b', 2: 'c', length: 3 }; // 配列のようなオブジェクト
Array.prototype.join.call(a33, '+'); // "a+b+c": 配列のようなオブジェクトに対してjoin()メソッドを呼び出す
Array.prototype.map.call(a33, (x) => x.toUpperCase()); // ["A", "B", "C"]: 配列のようなオブジェクトに対してmap()メソッドを呼び出す
Array.prototype.slice.call(a33, 0); // ["a", "b", "c"]: 配列のようなオブジェクトに対してslice()メソッドを呼び出し、間接的に配列を取得する
Array.from(a33); // ["a", "b", "c"]: 配列のようなオブジェクトを配列に変換する

7.10 配列としての文字列

JavaScriptの文字列はUTF-16のUnicode文字の読み出し専用の配列のようにふるまう つまり、個々の文字にアクセスする際にcharAt()メソッドの代わりに[]を使うことができる

ただし、配列ではないのでtypeof演算子で文字列を調べると"string"が返され、Array.isArray()メソッドで調べるとfalseが返される

ただ、配列のようにふるまうので、文字列に対してArray.prototypeのメソッドを使うことができる その際、文字列を含むプリミティブ値は不変であるため、push()やsort()などの破壊的なメソッドは使えない

let s = 'test';
s.charAt(0); // "t"
s[1]; // "e"

Array.prototype.join.call(s, '-'); // "t-e-s-t": 文字列に対してjoin()メソッドを呼び出す

7.11 まとめ

最後に配列のメソッドを破壊的・非破壊的メソッドにまとめる

分類メソッド名概要
破壊的push配列の末尾に要素を追加し、変更後の配列の長さを返します。
pop配列の末尾の要素を削除し、その削除された要素を返します。
shift配列の先頭の要素を削除し、その削除された要素を返します。
unshift配列の先頭に要素を追加し、変更後の配列の長さを返します。
splice配列の指定位置に要素を追加・削除します。元の配列を変更します。
sort配列をソートし、元の配列を変更します。
reverse配列の要素を逆順に並び替え、元の配列を変更します。
copyWithin配列の指定範囲をコピーして別の位置に上書きします(元の配列を変更)。
fill配列内の指定範囲を静的な値で埋めます(元の配列を変更)。
forEach配列の各要素に対してコールバック関数を適用します。
非破壊的concat2つ以上の配列を結合し、新しい配列を返します。
slice配列の指定範囲を抽出し、新しい配列を返します。
map各要素に対してコールバック関数を適用した結果の新しい配列を返します。
filter条件を満たす要素のみを含む新しい配列を返します。
reduce配列を1つの値に畳み込みます(元の配列は変更されません)。
reduceRight配列を右から左に畳み込みます(元の配列は変更されません)。
find条件を満たす最初の要素を返します(元の配列は変更されません)。
findIndex条件を満たす最初の要素のインデックスを返します。
includes指定された値が配列に含まれているかを真偽値で返します。
indexOf指定された値の最初のインデックスを返します。
lastIndexOf指定された値の最後のインデックスを返します。
every全ての要素が条件を満たすかどうかを真偽値で返します。
some少なくとも1つの要素が条件を満たすかを真偽値で返します。
join配列の全要素を文字列として結合し、1つの文字列を返します。
flatネストされた配列を指定された深さまで展開した新しい配列を返します。
flatMapmapとflatを組み合わせた新しい配列を返します。
toString配列を文字列に変換して返します。
toReversed配列を逆順にした新しい配列を返します(元の配列は変更されません)。
toSorted配列をソートした新しい配列を返します(元の配列は変更されません)。
toSpliced配列を指定範囲で操作した新しい配列を返します(元の配列は変更されません)。
with指定したインデックスの要素を置き換えた新しい配列を返します。