JavaScriptの基礎 - 配列の操作

提供: MochiuWiki : SUSE, EC, PCB

概要

JavaScriptの配列は、複数の値を順序付きリストとして格納するためのオブジェクトである。
インデックス (0から始まる整数) で各要素にアクセスでき、文字列、数値、オブジェクト、別の配列等、異なる型の値を1つの配列に混在させることができる。

JavaScriptの配列は動的なサイズを持つ。
事前にサイズを宣言する必要がなく、要素の追加や削除に応じて自動的に伸縮する。

配列は参照型であり、変数に格納されるのは配列そのものではなく、配列への参照である。
そのため、const で宣言した変数に格納された配列であっても、配列内の要素は変更できる。

JavaScriptには配列を操作するための豊富な組み込みメソッドが用意されている。
末尾への追加 (push)、先頭への追加 (unshift)、任意位置への挿入・削除 (splice) 等、様々な操作を簡潔に記述できる。

また、要素の検索には indexOfincludesfind 等の複数のメソッドが用意されており、用途に応じて使い分けられる。

ES2015 (ES6) 以降ではスプレッド構文や分割代入が導入され、配列のコピーや結合、変数への展開を簡潔に記述できるようになった。


配列の作成

JavaScriptで配列を作成する方法は主に2種類ある。
通常は配列リテラルを使用することが推奨される。

配列リテラル

配列リテラルは、角括弧 ([]) で要素を列挙することにより、配列を生成する記法である。
最もシンプルで可読性が高く、パフォーマンスにも優れる。

 // 基本的な配列リテラル
 const fruits  = ["apple", "banana", "orange"];
 const numbers = [1, 2, 3, 4, 5];
 
 // 空配列
 const empty = [];
 
 // 異なる型を混在できる
 const mixed = [1, "hello", true, null, { name: "Alice" }];
 
 // 配列のネスト (2次元配列)
 const matrix = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
 ];


Arrayコンストラクタ と Array.of

Array() コンストラクタを使用して配列を生成することもできるが、引数の個数によって動作が変わるため注意が必要である。

Array() コンストラクタの動作の違いを以下に示す。

  • 引数が1つの数値の場合
    その数値を配列の長さ (length) として解釈し、空スロットを持つ配列を生成する。
    例: new Array(5) は要素が5個の空配列を生成する。(要素は、undefined)
  • 引数が2つ以上、または数値以外の場合
    引数を配列の要素として扱う。
    例: new Array("a", "b") は、["a", "b"] を生成する。


Array.of()Array() コンストラクタの混乱を避けるために導入されたメソッドである。
引数の個数や型に関わらず、常に引数を要素として扱い、配列を生成する。

 // Array() コンストラクタの動作の違い
 const arr1 = new Array(5);       // [ <5 empty items> ] (長さ5の空配列)
 const arr2 = new Array(1, 2);    // [1, 2]
 const arr3 = [5];                // [5] (リテラルとの違い)
 
 // Array.of() は常に引数を要素として扱う
 const arr4 = Array.of(5);        // [5]
 const arr5 = Array.of(1, 2);     // [1, 2]
 const arr6 = Array.of(7, 8, 9);  // [7, 8, 9]



要素へのアクセス

配列の要素へアクセスする方法は複数ある。
インデックスによる直接アクセスが基本であるが、ES2022以降では at() メソッドも利用できる。

インデックスアクセス

配列の要素には、ゼロベースのインデックスを使用してアクセスする。
最初の要素のインデックスは 0、最後の要素のインデックスは length - 1 である。

存在しないインデックスを指定した場合は、undefined が返る。

 const fruits = ["apple", "banana", "orange"];
 
 console.log(fruits[0]);  // "apple"
 console.log(fruits[1]);  // "banana"
 console.log(fruits[2]);  // "orange"
 console.log(fruits[3]);  // undefined (存在しないインデックス)
 
 // 要素の書き換え
 fruits[1] = "grape";
 console.log(fruits);     // ["apple", "grape", "orange"]


atメソッド

at() メソッドはES2022で導入され、負のインデックスを使用して末尾から要素にアクセスできる。

負のインデックスの動作を以下に示す。

  • -1
    末尾から1番目の要素 (最後の要素)
  • -2
    末尾から2番目の要素
  • arr.at(-1)arr[arr.length - 1] と同等


 const fruits = ["apple", "banana", "orange", "grape"];
 
 // 正のインデックス (先頭から)
 console.log(fruits.at(0));   // "apple"
 console.log(fruits.at(1));   // "banana"
 
 // 負のインデックス (末尾から)
 console.log(fruits.at(-1));  // "grape" (最後の要素)
 console.log(fruits.at(-2));  // "orange"
 
 // 従来の書き方と比較
 console.log(fruits[fruits.length - 1]);  // "grape" (at(-1) と同等)


lengthプロパティ

length プロパティは配列の要素数を返す。
読み取りだけでなく書き込みも可能であり、length を小さい値に設定することで配列を切り詰めることができる。

 const fruits = ["apple", "banana", "orange"];
 
 // 要素数の取得
 console.log(fruits.length);              // 3
 
 // lengthを使った末尾へのアクセス
 console.log(fruits[fruits.length - 1]);  // "orange"
 
 // lengthを設定して配列を切り詰める
 fruits.length = 2;
 console.log(fruits);                     // ["apple", "banana"]
 
 // lengthを0に設定して空にする
 fruits.length = 0;
 console.log(fruits);                     // []



要素の追加と削除

JavaScriptでは、配列の末尾・先頭・任意の位置に要素を追加・削除するためのメソッドが用意されている。
これらのメソッドは配列を直接変更する破壊的メソッドである。

push と pop

push()pop() は、配列の末尾を操作するメソッドである。

各メソッドの動作を以下に示す。

  • push()
    配列の末尾に1つ以上の要素を追加する。
    戻り値は追加後の新しい length
  • pop()
    配列の末尾の要素を削除し、その要素を返す。
    空配列に対して呼び出した場合は、undefined を返す。


 const fruits = ["apple", "banana"];
 
 // push : 末尾に追加
 const newLength = fruits.push("orange");
 console.log(fruits);     // ["apple", "banana", "orange"]
 console.log(newLength);  // 3 (新しい length)
 
 // 複数要素を1度に追加
 fruits.push("grape", "melon");
 console.log(fruits);     // ["apple", "banana", "orange", "grape", "melon"]
 
 // pop : 末尾から削除
 const removed = fruits.pop();
 console.log(removed);    // "melon" (削除された要素)
 console.log(fruits);     // ["apple", "banana", "orange", "grape"]
 
 // 空配列でpop
 const empty = [];
 console.log(empty.pop()); // undefined


unshift と shift

unshift()shift() は配列の先頭を操作するメソッドである。
先頭への操作は既存の全要素のインデックスを変更するため、配列が大きいほど処理コスト O(n) が掛かる。

各メソッドの動作を以下に示す。

  • unshift()
    配列の先頭に1つ以上の要素を追加する。
    戻り値は追加後の新しい length
    複数の要素を渡した場合、引数の順序を保って先頭に追加される。
  • shift()
    配列の先頭の要素を削除し、その要素を返す。
    空配列に対して呼び出した場合は undefined を返す。


 const fruits = ["banana", "orange"];
 
 // unshift : 先頭に追加
 const newLength = fruits.unshift("apple");
 console.log(fruits);     // ["apple", "banana", "orange"]
 console.log(newLength);  // 3
 
 // 複数要素を1度に追加 (順序が保たれる)
 fruits.unshift("fig", "grape");
 console.log(fruits);  // ["fig", "grape", "apple", "banana", "orange"]
 
 // shift : 先頭から削除
 const removed = fruits.shift();
 console.log(removed);  // "fig"
 console.log(fruits);   // ["grape", "apple", "banana", "orange"]


splice

splice() は配列の任意の位置に対して要素の追加、削除、置換を行うことができる汎用メソッドである。
元の配列を直接変更する破壊的メソッドであり、削除した要素を配列として返す。

構文を以下に示す。

  • arr.splice(開始位置, 削除数, 挿入要素...)
    開始位置
    操作を開始するインデックス。負の値を指定すると末尾から数える。
    削除数
    削除する要素の個数。0 を指定すると削除しない。
    挿入要素
    開始位置に挿入する要素。(省略可能)


 const fruits = ["apple", "banana", "orange", "grape"];
 
 // 要素の削除 : インデックス1から2個削除
 const removed = fruits.splice(1, 2);
 console.log(removed);  // ["banana", "orange"]
 console.log(fruits);   // ["apple", "grape"]
 
 // 要素の挿入 : インデックス1の位置に挿入 (削除なし)
 fruits.splice(1, 0, "fig", "melon");
 console.log(fruits);   // ["apple", "fig", "melon", "grape"]
 
 // 要素の置換 : インデックス1から1個削除して2個挿入
 fruits.splice(1, 1, "kiwi", "lemon");
 console.log(fruits);   // ["apple", "kiwi", "lemon", "melon", "grape"]
 
 // 負のインデックス : 末尾から2番目から1個削除
 fruits.splice(-2, 1);
 console.log(fruits);   // ["apple", "kiwi", "lemon", "grape"]



配列の一部を取得する

配列から特定の範囲の要素を取り出す時には、slice() メソッドを使用する。

slice

slice() は指定した範囲の要素を含む新しい配列を返す非破壊的メソッドである。
元の配列は変更されない。

構文を以下に示す。

  • arr.slice(開始位置, 終了位置)
    開始位置
    取り出しを開始するインデックス
    省略すると 0
    終了位置
    取り出しを終了するインデックス (この位置の要素は含まない)
    省略すると末尾まで
    負の値を指定すると末尾から数えたインデックスとして解釈される。


 const fruits = ["apple", "banana", "orange", "grape", "melon"];
 
 // 基本的な使い方
 console.log(fruits.slice(1, 3));    // ["banana", "orange"] (インデックス3は含まない)
 console.log(fruits.slice(2));       // ["orange", "grape", "melon"] (末尾まで)
 console.log(fruits.slice(0, 2));    // ["apple", "banana"]
 
 // 負のインデックス
 console.log(fruits.slice(-2));      // ["grape", "melon"] (末尾から2個)
 console.log(fruits.slice(-3, -1));  // ["orange", "grape"]
 
 // 引数無しで浅いコピーを生成
 const copy = fruits.slice();
 console.log(copy);                  // ["apple", "banana", "orange", "grape", "melon"]
 console.log(copy === fruits);       // false (異なるオブジェクト)
 
 // 元の配列は変更されない
 console.log(fruits);                // ["apple", "banana", "orange", "grape", "melon"]


下表に、splice()slice() の違いを示す。

splice と slice の比較
メソッド 破壊的か 戻り値 主な用途
splice() 破壊的 (元配列を変更する) 削除された要素の配列 要素の追加・削除・置換
slice() 非破壊的 (元配列を変更しない) 新しい配列 部分配列の抽出・配列のコピー



要素の検索

配列内の要素を検索するためのメソッドが複数用意されている。
検索条件や返したい値の種類に応じて使い分けることが重要である。

indexOf と lastIndexOf

indexOf()lastIndexOf() は、指定した値と一致する要素のインデックスを返すメソッドである。
厳密等価 (===) を使用して比較するため、型も一致していなければならない。

各メソッドの動作を以下に示す。

  • indexOf(値, 開始インデックス)
    先頭から検索し、最初に一致した要素のインデックスを返す。
    見つからない場合は -1 を返す。
  • lastIndexOf(値, 開始インデックス)
    末尾から検索し、最後に一致した要素のインデックスを返す。
    見つからない場合は -1 を返す。
  • NaN の検索
    厳密等価では NaN === NaNfalse になるため、これらのメソッドでは NaN を正しく検索できない。


 const fruits = ["apple", "banana", "orange", "banana", "grape"];
 
 // indexOf : 先頭から検索
 console.log(fruits.indexOf("banana"));     // 1 (最初に見つかった位置)
 console.log(fruits.indexOf("banana", 2));  // 3 (インデックス2以降で検索)
 console.log(fruits.indexOf("melon"));      // -1 (見つからない)
 
 // lastIndexOf : 末尾から検索
 console.log(fruits.lastIndexOf("banana")); // 3 (最後に見つかった位置)
 console.log(fruits.lastIndexOf("apple"));  // 0
 
 // 型の不一致では見つからない
 const numbers = [1, 2, 3];
 console.log(numbers.indexOf("1"));  // -1 (文字列 "1" は数値 1 と一致しない)
 
 // NaNはindexOf では検索できない
 const arr = [1, NaN, 3];
 console.log(arr.indexOf(NaN));  // -1 (正しく検索できない)


includes

includes() は配列に指定した値が含まれているかどうかを真偽値で返すメソッドである。
SameValueZeroアルゴリズムを使用するため、NaN も正しく検索できる。

 const fruits = ["apple", "banana", "orange"];
 
 // 基本的な使用方法
 console.log(fruits.includes("banana"));    // true
 console.log(fruits.includes("melon"));     // false
 
 // 開始インデックスの指定
 console.log(fruits.includes("apple", 1));  // false (インデックス1以降で検索)
 
 // NaNの検索
 const arr = [1, NaN, 3];
 console.log(arr.includes(NaN));            // true (NaNを正しく検索できる)
 console.log(arr.indexOf(NaN) !== -1);      // false (indexOfでは検索できない)


find と findIndex

find()findIndex() は、コールバック関数を使用して柔軟な条件で要素を検索するメソッドである。
単純な値の一致だけでなく、任意の条件に基づいて検索できる。

各メソッドの動作を以下に示す。

  • find(コールバック)
    コールバック関数が true を返した最初の要素を返す。
    見つからない場合は undefined を返す。
  • findIndex(コールバック)
    コールバック関数が true を返した最初の要素のインデックスを返す。
    見つからない場合は -1 を返す。


 const numbers = [5, 12, 8, 130, 44];
 
 // find : 条件を満たす最初の要素を返す
 const found = numbers.find((value) => value > 10);
 console.log(found);  // 12
 
 // findIndex : 条件を満たす最初の要素のインデックスを返す
 const foundIndex = numbers.findIndex((value) => value > 10);
 console.log(foundIndex);  // 1
 
 // オブジェクトの配列での検索
 const users = [
    { id: 1, name: "Alice" },
    { id: 2, name: "Bob" },
    { id: 3, name: "Charlie" }
 ];
 
 const user = users.find((u) => u.id === 2);
 console.log(user);       // { id: 2, name: "Bob" }
 
 const userIndex = users.findIndex((u) => u.name === "Charlie");
 console.log(userIndex);  // 2
 
 // 見つからない場合
 console.log(numbers.find((value) => value > 1000));       // undefined
 console.log(numbers.findIndex((value) => value > 1000));  // -1



スプレッド構文

スプレッド構文 (...) は、配列やイテラブルオブジェクトを展開するための記法である。
ES2015 (ES6) で導入され、配列のコピーや結合、関数への引数渡しを簡潔に記述できる。

スプレッド構文の主な用途を以下に示す。

  • 配列の浅いコピー
    const copy = [...arr]
    ネストしたオブジェクトや配列は参照がコピーされる。(深いコピーではない)
  • 配列の結合
    const combined = [...arr1, ...arr2]
    複数の配列を新しい配列にまとめることができる。
  • 関数への引数展開
    Math.max(...arr)
    配列の要素を個別の引数として渡すことができる。
  • 配列への要素挿入
    const newArr = [...arr1, 新要素, ...arr2]


 const arr1 = [1, 2, 3];
 const arr2 = [4, 5, 6];
 
 // 配列の浅いコピー
 const copy = [...arr1];
 console.log(copy);        // [1, 2, 3]
 console.log(copy === arr1); // false (異なるオブジェクト)
 
 // 配列の結合
 const combined = [...arr1, ...arr2];
 console.log(combined);    // [1, 2, 3, 4, 5, 6]
 
 // 要素を間に挿入して結合
 const inserted = [...arr1, 99, ...arr2];
 console.log(inserted);    // [1, 2, 3, 99, 4, 5, 6]
 
 // 関数への引数展開
 const numbers = [3, 1, 4, 1, 5, 9];
 console.log(Math.max(...numbers));  // 9
 console.log(Math.min(...numbers));  // 1
 
 // 文字列を配列に変換
 const chars = [..."hello"];
 console.log(chars);  // ["h", "e", "l", "l", "o"]



分割代入

分割代入 (Destructuring Assignment) は、配列やオブジェクトから値を取り出して変数に代入するための構文である。
ES2015 (ES6) で導入され、配列の要素を個別の変数に展開する操作を簡潔に記述できる。

分割代入の主な用途を以下に示す。

  • 基本的な分割代入
    配列の要素を順番に変数に代入する。
  • 要素のスキップ
    カンマを連続させることで、不要な要素をスキップできる。
  • レストパターン
    ... を使用して残りの要素をまとめて配列として受け取る。
  • デフォルト値の設定
    要素が undefined の場合に使用するデフォルト値を指定できる。
  • 値の入れ替え
    一時変数を使わずに2つの変数の値を入れ替えることができる。


 // 基本的な分割代入
 const [a, b, c] = [10, 20, 30];
 console.log(a, b, c);  // 10 20 30
 
 // 要素のスキップ (カンマで位置を空ける)
 const [first, , third] = [1, 2, 3];
 console.log(first, third);  // 1 3
 
 // レストパターン (残りの要素をまとめて受け取る)
 const [head, ...tail] = [1, 2, 3, 4, 5];
 console.log(head);  // 1
 console.log(tail);  // [2, 3, 4, 5]
 
 // デフォルト値の設定
 const [x = 10, y = 20] = [5];
 console.log(x, y);  // 5 20 (y は undefined なのでデフォルト値 20 が使われる)
 
 // 値の入れ替え (一時変数不要)
 let p = 1;
 let q = 2;
 [p, q] = [q, p];
 console.log(p, q);  // 2 1
 
 // 関数の戻り値からの分割代入
 function getCoordinates() {
    return [35.6895, 139.6917];
 }
 
 const [latitude, longitude] = getCoordinates();
 console.log(latitude, longitude);  // 35.6895 139.6917



関連情報