JavaScript配列の基本:追加、削除、ループ、検索まで徹底解説

JavaScriptプログラミングにおいて、データを効率的に管理するための最も基本的な構造の一つが「配列」です。複数の値をまとめて扱いたい場合や、リスト形式のデータを扱う際に不可欠な存在となります。

この記事では、JavaScript配列の基本から、要素の追加・削除、ループ処理、そして検索やコピーといった応用的な使い方まで、具体例を交えながら徹底的に解説していきます。配列をマスターして、より堅牢で効率的なJavaScriptコードを書きましょう。

JavaScript配列とは?基本を理解しよう

配列の役割と基本的な考え方

JavaScriptの配列は、複数の値を順序付けて格納できる特別な種類のオブジェクトです。これらの値は「要素」と呼ばれ、それぞれが0から始まるインデックス(添字)によって識別されます。例えば、ユーザー名のリスト、商品の価格一覧、タスクの進捗状況など、関連するデータをまとめて管理するのに非常に役立ちます。

配列を使うことで、個々の変数にデータを格納するよりも、コードが簡潔になり、データの操作も格段に効率的になります。特に、大量のデータを扱うアプリケーション開発では、配列の理解と適切な利用がパフォーマンスと保守性の向上に直結します。

イメージとしては、一つの箱の中に順番に並べられた複数のアイテムといったところでしょうか。各アイテムには「1番目のアイテム」「2番目のアイテム」といった番号が振られている、と考えると分かりやすいでしょう。

配列とオブジェクトの違い

JavaScriptには配列とよく似た構造としてオブジェクトがありますが、両者には明確な違いがあります。オブジェクトはキーと値のペアでデータを格納するのに対し、配列は数値のインデックス(0から始まる連続した整数)で値にアクセスします。

オブジェクトはプロパティ名で意味のあるデータを表現するのに適していますが、配列は順序性のあるデータのコレクションを扱うのに最適です。例えば、{ name: "Alice", age: 30 }はオブジェクトですが、["Alice", "Bob", "Charlie"]は配列です。

配列は内部的にはオブジェクトの一種であり、typeof []とすると"object"が返りますが、これは配列がJavaScriptの内部実装でオブジェクトとして扱われているためです。用途に応じて、順序性が必要な場合は配列、キーによる識別が必要な場合はオブジェクトと使い分けることが重要です。

配列のデータ型と多様性

JavaScriptの配列は、非常に柔軟なデータ構造を持っています。C++やJavaなどの厳格な型付け言語とは異なり、一つの配列の中に異なるデータ型の要素を混在させることができます。

例えば、文字列、数値、真偽値、オブジェクト、さらには他の配列(多次元配列)を同じ配列の要素として格納することが可能です。

const mixedArray = ["Hello", 123, true, { id: 1 }, [1, 2]];
console.log(mixedArray[0]); // "Hello" (文字列)
console.log(mixedArray[1]); // 123 (数値)
console.log(mixedArray[3].id); // 1 (オブジェクト内のプロパティ)

この多様性は、開発において非常に便利ですが、同時に配列の要素を操作する際には、それぞれのデータ型を意識する必要があります。これにより、より複雑なデータ構造も配列一つで表現できるようになり、柔軟なプログラミングが可能になります。

配列の初期化と要素数の確認方法

配列の宣言と初期値

JavaScriptで配列を初期化する最も一般的で推奨される方法は、配列リテラルを使用することです。これは角括弧[]を使って表現します。

const emptyArray = []; // 空の配列
const fruits = ["apple", "banana", "cherry"]; // 初期値を持つ配列
const numbers = [1, 2, 3, 4, 5];

このように宣言された配列は、すぐに使用できます。また、letキーワードを使って宣言すれば、後から要素を追加したり変更したりすることも可能です。

constで宣言した場合は、配列そのものの参照は変更できませんが、配列内の要素は変更できます。これはJavaScriptの変数宣言の重要なポイントです。配列リテラルは直感的で読みやすく、ほとんどのケースでこの方法が選ばれます。

便利なArrayコンストラクタ

配列リテラルの他に、Arrayコンストラクタを使用して配列を初期化することもできます。

const arr1 = new Array(); // 空の配列
const arr2 = new Array(5); // 長さ5の空の配列 (要素はundefined)
const arr3 = new Array("apple", "banana"); // 初期値を持つ配列

new Array(length)のように数値を1つだけ引数に渡した場合、その数値は配列の初期の長さを示し、その長さ分のundefined要素を持つ配列が生成されます。これは、事前に配列のサイズが分かっている場合にメモリを確保する意図で使用されることがありますが、通常は配列リテラルのほうが直感的で誤解が少ないため推奨されます。

しかし、特定のシナリオ、例えば固定長の配列を効率的に作成したい場合には、Arrayコンストラクタが役立つこともあります。

lengthプロパティで要素数を取得

配列の要素数を取得するには、lengthプロパティを使用します。これは配列に存在する要素の数を返す組み込みのプロパティです。

const colors = ["red", "green", "blue"];
console.log(colors.length); // 出力: 3

const emptyArr = [];
console.log(emptyArr.length); // 出力: 0

lengthプロパティは、配列のループ処理の終了条件として頻繁に使用されます。また、配列の末尾に新しい要素を追加する際にも、arr[arr.length] = newValue;のように使うことで、新しい要素を配列の最後尾に効率的に追加できます。

このプロパティは配列の現在の状態を把握するために不可欠であり、JavaScriptで配列を扱う上で最も基本的な操作の一つです。(出典: MDN Web Docs)

要素の追加・削除:自在に配列を操作

push, unshiftで要素を追加

配列に新しい要素を追加する最も一般的な方法は、push()メソッドとunshift()メソッドを使用することです。

push()メソッドは、配列の末尾に一つまたは複数の要素を追加し、新しい配列の長さを返します。

const fruits = ["apple", "banana"];
fruits.push("cherry"); // fruitsは ["apple", "banana", "cherry"] に
fruits.push("grape", "kiwi"); // fruitsは ["apple", "banana", "cherry", "grape", "kiwi"] に
console.log(fruits);

一方、unshift()メソッドは、配列の先頭に一つまたは複数の要素を追加し、新しい配列の長さを返します。

const numbers = [2, 3];
numbers.unshift(1); // numbersは [1, 2, 3] に
numbers.unshift(0); // numbersは [0, 1, 2, 3] に
console.log(numbers);

unshift()は要素を先頭に追加するため、既存の全要素のインデックスが変更されるため、push()よりも処理コストが高くなる可能性があります。

pop, shiftで要素を削除

配列から要素を削除する基本的な方法には、pop()メソッドとshift()メソッドがあります。

pop()メソッドは、配列の末尾から最後の要素を削除し、その削除された要素を返します。配列の長さを短くします。

const animals = ["cat", "dog", "elephant"];
const removedAnimal = animals.pop(); // removedAnimalは "elephant", animalsは ["cat", "dog"] に
console.log(animals);
console.log(removedAnimal);

shift()メソッドは、配列の先頭から最初の要素を削除し、その削除された要素を返します。こちらも配列の長さを短くします。

const colors = ["red", "green", "blue"];
const removedColor = colors.shift(); // removedColorは "red", colorsは ["green", "blue"] に
console.log(colors);
console.log(removedColor);

shift()unshift()と同様に、配列の全要素のインデックスを再調整する必要があるため、pop()よりも処理コストが高くなる傾向があります。

spliceで任意の場所を操作

splice()メソッドは、配列の指定した位置から要素を追加、削除、または置換する非常に強力なメソッドです。これは配列操作の「万能ツール」とも言えます。

構文は array.splice(start, deleteCount, item1, item2, ...) となります。

  • start: 変更を開始するインデックス。
  • deleteCount: startから削除する要素の数。0を指定すると削除は行われません。
  • item1, item2, ...: 削除した位置に追加する要素。
const cars = ["BMW", "Mercedes", "Audi", "Toyota", "Honda"];

// 1. 要素の削除: インデックス2から2つの要素を削除
cars.splice(2, 2); // carsは ["BMW", "Mercedes", "Honda"] に
console.log(cars);

// 2. 要素の追加: インデックス1に要素を追加 (削除なし)
cars.splice(1, 0, "Volvo", "Nissan"); // carsは ["BMW", "Volvo", "Nissan", "Mercedes", "Honda"] に
console.log(cars);

// 3. 要素の置換: インデックス3から1つの要素を削除し、新しい要素を追加
cars.splice(3, 1, "Mazda"); // carsは ["BMW", "Volvo", "Nissan", "Mazda", "Honda"] に
console.log(cars);

splice()は元の配列を変更する「破壊的」なメソッドである点に注意が必要です。これにより、一つのメソッドで多様な配列操作が可能になります。

配列のループ処理:効率的にデータを扱う

forループとfor...ofループ

JavaScriptで配列の要素を反復処理する最も基本的な方法は、伝統的なforループを使用することです。これはインデックスを使って各要素にアクセスします。

const items = ["A", "B", "C"];
for (let i = 0; i < items.length; i++) {
    console.log(`インデックス ${i}: ${items[i]}`);
}

この方法は柔軟性が高いですが、ES6で導入されたfor...ofループは、配列の要素に直接アクセスできるため、より簡潔で読みやすいコードを書くことができます。インデックスは不要で、値そのものに注目したい場合に最適です。

for (const item of items) {
    console.log(`要素: ${item}`);
}

どちらのループも有効ですが、要素の値のみが必要な場合はfor...ofが推奨されます。

forEachメソッドを使った反復処理

配列の各要素に対して特定の処理を行いたい場合、Array.prototype.forEach()メソッドが非常に便利です。これはコールバック関数を受け取り、配列の各要素に対して一度だけその関数を実行します。

const products = ["Laptop", "Mouse", "Keyboard"];
products.forEach(function(product, index) {
    console.log(`${index + 1}. ${product}`);
});

// アロー関数を使うとさらに簡潔に
products.forEach((product) => console.log(`商品名: ${product}`));

forEach()は戻り値を返さないため、配列を変換したり、新しい配列を作成したりする目的には適していません。単に各要素に対して副作用(例:コンソール出力、DOM操作など)を実行したい場合に最適です。ループを途中で中断することはできません。

map, filter, reduceで高度な処理

JavaScriptの配列には、より高度なデータ変換や集計を行うための強力なメソッドがいくつか用意されています。これらは「高階関数」と呼ばれ、関数型プログラミングの概念に基づいており、宣言的で読みやすいコードを書くのに役立ちます。

  • map(): 配列の各要素に関数を適用し、その結果から新しい配列を生成します。元の配列は変更されません。
    const numbers = [1, 2, 3];
    const doubledNumbers = numbers.map(num => num * 2); // [2, 4, 6]
  • filter(): 配列の各要素に対して条件をテストし、その条件を満たす要素のみを含む新しい配列を生成します。
    const ages = [10, 20, 30, 15];
    const adults = ages.filter(age => age >= 20); // [20, 30]
  • reduce(): 配列の全要素を単一の値に集約します。合計値の算出や、複雑なデータ構造のフラット化などに使われます。
    const prices = [100, 200, 300];
    const total = prices.reduce((sum, currentPrice) => sum + currentPrice, 0); // 600

これらのメソッドを使いこなすことで、より洗練された配列操作が可能になります。

配列の検索・存在チェック・コピー:さらに便利に使いこなす

indexOf, includesで要素を検索

配列内に特定の要素が存在するかどうかを確認したり、その要素のインデックスを知りたい場合は、indexOf()includes()メソッドが役立ちます。

indexOf()メソッドは、指定した要素が配列内で最初に見つかったインデックスを返します。見つからない場合は-1を返します。

const fruits = ["apple", "banana", "cherry", "banana"];
console.log(fruits.indexOf("banana")); // 1
console.log(fruits.indexOf("grape"));  // -1

includes()メソッドは、配列が特定の要素を含んでいるかどうかを真偽値(true/false)で返します。よりシンプルに存在チェックを行いたい場合に便利です。

console.log(fruits.includes("cherry")); // true
console.log(fruits.includes("mango"));  // false

これらは単純な値の比較に使用され、オブジェクトや配列の参照比較では注意が必要です。

find, findIndexで条件に合う要素を探す

indexOf()includes()が単純な値の一致をチェックするのに対し、find()findIndex()は、より複雑な条件に基づいて要素を検索するのに使用されます。これらのメソッドはコールバック関数を受け取り、その関数がtrueを返した最初の要素(またはそのインデックス)を返します。

find()メソッドは、指定されたテスト関数を満たす配列内の最初の要素の値を返します。見つからない場合はundefinedを返します。

const users = [
    { id: 1, name: "Alice" },
    { id: 2, name: "Bob" },
    { id: 3, name: "Alice" }
];
const alice = users.find(user => user.name === "Alice");
console.log(alice); // { id: 1, name: "Alice" } (最初のAliceを返す)

findIndex()メソッドは、指定されたテスト関数を満たす配列内の最初の要素のインデックスを返します。見つからない場合は-1を返します。

const bobIndex = users.findIndex(user => user.name === "Bob");
console.log(bobIndex); // 1
const charlieIndex = users.findIndex(user => user.name === "Charlie");
console.log(charlieIndex); // -1

これらはオブジェクトの配列など、複雑なデータ構造から特定の条件に合致する要素を探す際に非常に強力です。

配列のコピー方法:浅いコピーと深いコピー

JavaScriptで配列をコピーする際には、「浅いコピー」と「深いコピー」の違いを理解することが重要です。

浅いコピーは、新しい配列を作成しますが、その新しい配列は元の配列の要素(参照型の場合)への参照を共有します。つまり、オブジェクトがネストしている場合、内側のオブジェクトはコピーされず、両方の配列が同じオブジェクトを参照することになります。

一般的な浅いコピーの方法:

  • スプレッド構文: const newArr = [...oldArr];
  • slice()メソッド: const newArr = oldArr.slice();
  • Array.from(): const newArr = Array.from(oldArr);
const original = [{ id: 1 }];
const shallowCopy = [...original];
shallowCopy[0].id = 2;
console.log(original[0].id); // 2 (元の配列も変更される)

深いコピーは、元の配列とその中のすべてのネストされたオブジェクトや配列を完全に独立した新しい構造として複製します。これにより、コピーを変更しても元の配列には影響しません。JavaScriptで汎用的な深いコピーを行うには、通常、外部ライブラリを使用するか、JSON.parse(JSON.stringify(original))のような方法を使いますが、この方法は関数やundefinedのような特定の値を失う可能性があります。

より確実な方法としては、現代のブラウザやNode.jsで利用可能なstructuredClone()関数を使用することが推奨されます。これは、JavaScriptの構造化クローンアルゴリズムに基づいており、より多くのデータ型を正確にコピーできます。(出典: MDN Web Docs)

// 例: structuredClone() はモダン環境で利用可能
// const original = [{ id: 1, data: [10, 20] }];
// const deepCopy = structuredClone(original);
// deepCopy[0].id = 2;
// deepCopy[0].data[0] = 100;
// console.log(original[0].id); // 1
// console.log(original[0].data[0]); // 10

ネストされたオブジェクトを扱う場合は、深いコピーの必要性を検討しましょう。