JavaScriptにおける配列操作は、アプリケーション開発において最も頻繁に行われる処理の一つと言っても過言ではありません。
中でも「特定の要素が配列内に存在するかどうか」を確認する作業は、データのバリデーションや条件分岐のトリガーとして極めて重要な役割を果たします。
かつてはループ処理や特定のメソッドを工夫して組み合わせていた処理も、現代のJavaScript(ECMAScript)では用途に合わせて洗練されたメソッドが数多く用意されています。
本記事では、単純な値の比較から、オブジェクトを含む複雑な条件判定まで、状況に応じた最適な「存在チェック」の方法を詳しく解説します。
基本の判定:includesメソッド
JavaScriptで最も直感的かつ簡潔に配列の存在チェックを行えるのが、ES2016で導入されたincludes()メソッドです。
このメソッドは、配列の中に指定した要素が含まれているかどうかを真偽値(trueまたはfalse)で返します。
構文が非常にシンプルであるため、可読性が高く、モダンなフロントエンド開発において第一選択肢となる手法です。
const fruits = ['apple', 'banana', 'orange'];
// 要素が含まれているか確認
const hasApple = fruits.includes('apple');
console.log(hasApple);
// 存在しない要素を確認
const hasGrape = fruits.includes('grape');
console.log(hasGrape);
true
false
includes()の大きな特徴として、NaN(Not a Number)を正しく判定できるという点が挙げられます。
以前主流だった手法ではNaNの判定に失敗することがありましたが、includes()を使えばその心配はありません。
また、第二引数に検索を開始するインデックスを指定することも可能です。
ただし、includes()は「値そのもの」を比較するため、参照型であるオブジェクトや配列の存在チェックには向いていないことに注意してください。
位置も同時に知りたい場合:indexOfメソッド
includes()が登場する以前、配列の存在チェックに最も広く使われていたのがindexOf()メソッドです。
このメソッドは、指定した要素が配列の何番目にあるかを数値で返し、要素が存在しない場合には「-1」を返します。
const languages = ['JavaScript', 'Python', 'Go'];
// 存在する要素のインデックスを取得
const index = languages.indexOf('Python');
if (index !== -1) {
console.log('要素が見つかりました。インデックス:' + index);
} else {
console.log('要素は見つかりませんでした。');
}
要素が見つかりました。インデックス:1
単に存在するかどうかを知りたいだけであればincludes()の方が適していますが、見つかった位置を後続の処理で利用したい場合にはindexOf()が有効です。
注意点として、indexOf()の結果を条件式で評価する際、if (languages.indexOf('JavaScript'))のように記述してはいけません。
なぜなら、インデックス「0」は見つかったことを意味しますが、JavaScriptの条件評価では「false」として扱われてしまうためです。
そのため、必ず「-1と比較する」という記述を徹底する必要があります。
複雑な条件での判定:someメソッド
配列の要素が単純な文字列や数値ではなく、オブジェクトである場合は、includes()やindexOf()では正しく判定できません。
そのような場合に非常に便利なのがsome()メソッドです。
some()は、配列の少なくとも一つの要素が、指定したテスト用関数(コールバック関数)を満たすかどうかを判定します。
const users = [
{ id: 1, name: 'Tanaka', active: true },
{ id: 2, name: 'Sato', active: false },
{ id: 3, name: 'Suzuki', active: false }
];
// activeがtrueのユーザーが存在するかチェック
const hasActiveUser = users.some(user => user.active === true);
console.log(hasActiveUser);
// 特定のIDを持つユーザーが存在するかチェック
const hasIdFive = users.some(user => user.id === 5);
console.log(hasIdFive);
true
false
some()の優れた点は、条件に合致する要素が見つかった瞬間に反復処理を中断する(ショートサーキット評価)点にあります。
これにより、巨大な配列を扱う際でも、無駄な計算を省き効率的にチェックを行うことができます。
特定のプロパティ値を確認したい、あるいは特定の条件(例:数値が100以上など)に合致するデータがあるかを確認したいシーンで最も威力を発揮します。
値そのものを取り出す:findメソッド
存在チェックを行うと同時に、そのデータ自体を取得して利用したい場合はfind()メソッドが最適です。
find()は、条件に一致する最初の要素を返し、見つからない場合はundefinedを返します。
const products = [
{ code: 'A01', price: 100 },
{ code: 'B02', price: 200 },
{ code: 'C03', price: 300 }
];
const targetCode = 'B02';
const foundProduct = products.find(product => product.code === targetCode);
if (foundProduct) {
console.log('商品が見つかりました。価格は' + foundProduct.price + '円です。');
} else {
console.log('該当する商品はありません。');
}
商品が見つかりました。価格は200円です。
存在の有無だけを真偽値で得たいのであればsome()の方が意味的に正しいですが、見つかったオブジェクトのプロパティを参照したい場合にはfind()を使うのが自然です。
ただし、配列の要素自体がnullやfalseなどの「偽値(Falsy)」である可能性がある場合、if (foundProduct)という判定は誤動作の元になります。
そのようなケースでは、厳密にundefinedと比較するようにしてください。
インデックスを取得する高度な方法:findIndexメソッド
オブジェクトの配列において、特定の条件を満たす要素が「何番目にあるか」を知りたいときは、findIndex()を利用します。
これはindexOf()のコールバック関数版と言えるメソッドです。
const tasks = [
{ id: 101, title: 'メール送信', done: true },
{ id: 102, title: '資料作成', done: false },
{ id: 103, title: '会議', done: false }
];
// idが102のタスクが何番目かを探す
const targetIndex = tasks.findIndex(task => task.id === 102);
if (targetIndex !== -1) {
console.log('インデックス' + targetIndex + '番目にあります。');
}
インデックス1番目にあります。
このメソッドもindexOf()と同様、見つからない場合には「-1」を返します。
特定の要素を配列から削除したい(spliceを使用したい)場合などに、このfindIndex()で位置を特定する手法がよく用いられます。
末尾から検索する最新の手法:findLastとfindLastIndex
ES2023からは、配列を末尾から検索するためのfindLast()とfindLastIndex()が導入されました。
これまでは配列を反転(reverse)させてから検索するか、従来のfor文を後ろから回す必要がありましたが、これらを使えば簡潔に記述できます。
const numbers = [5, 12, 8, 130, 44, 200];
// 10より大きい、最後の要素を取得
const lastLargeNumber = numbers.findLast(n => n > 10);
console.log(lastLargeNumber);
200
ログの履歴データや、時系列順に並んだ配列の中から「最新のデータ」を検索したい場合に非常に有用です。
元の配列を変更することなく末尾検索ができるため、副作用を抑えた安全なコードを書くことができます。
大量のデータを高速にチェックする:Setオブジェクトの活用
ここまでは配列の標準メソッドを紹介してきましたが、データ量が非常に多い場合にはパフォーマンスが課題になります。
配列のメソッド(includesやsome)は、内部的に配列を先頭から順に走査するため、要素数nに対して計算量がO(n)となります。
一方で、JavaScriptのSetオブジェクトを使用すると、計算量O(1)で存在チェックが可能です。
const hugeData = new Set(['id_001', 'id_002', 'id_003', /* ...数万件のデータ */]);
// 高速に存在チェック
if (hugeData.has('id_001')) {
console.log('存在します。');
}
存在します。
数千、数万といった膨大なリストに対して繰り返し検索を行うような処理では、配列を一度Setに変換してからhas()メソッドでチェックすることを検討してください。
ただし、Setへの変換自体にはコストがかかるため、一度きりの検索であれば配列メソッドの方が早い場合もあります。
「頻繁に検索される固定リスト」などの用途において、この手法は絶大な効果を発揮します。
手法の比較まとめ
それぞれのメソッドの特性を整理し、適切な使い分けを判断するための比較表を以下に示します。
| メソッド名 | 戻り値 | 主な用途 | 複雑な条件 |
|---|---|---|---|
| includes | Boolean | プリミティブ値の単純な存在確認 | 不可 |
| some | Boolean | オブジェクトや条件式の存在確認 | 可能 |
| indexOf | Number | 値の位置(インデックス)を取得 | 不可 |
| find | Any / undefined | 条件に合う要素そのものを取得 | 可能 |
| findIndex | Number | 条件に合う要素の位置を取得 | 可能 |
| Set.has | Boolean | 大量データに対する超高速検索 | 不可 |
このように、目的が「単なる有無」なのか「データの取得」なのか、対象が「単純な値」なのか「オブジェクト」なのかによって使い分けることが重要です。
注意すべきエッジケース:疎な配列と型変換
配列の存在チェックを行う際、予期せぬ挙動を防ぐために知っておくべき注意点があります。
一つは、「疎な配列(空きスロットがある配列)」の扱いです。
includes()は空きスロットをundefinedとして扱いますが、indexOf()は空きスロットを無視するという違いがあります。
const sparseArray = [1, , 3]; // インデックス1は空きスロット
console.log(sparseArray.includes(undefined));
console.log(sparseArray.indexOf(undefined));
true
-1
もう一つの注意点は、厳密等価判定(===)で行われるという点です。
数値の1と文字列の'1'は区別されるため、外部APIやフォームから取得したデータの型が混在している場合は、適切にキャスト(型変換)を行ってからチェックする必要があります。
まとめ
JavaScriptで配列の存在チェックを行う方法は多岐にわたりますが、基本的にはincludes()をベースとし、より複雑な条件が必要な場合にsome()やfind()を選択するのがベストプラクティスです。
モダンな開発においては、indexOf() !== -1という記述よりも、意図が明確で可読性の高いincludes()やsome()が推奨されます。
また、大規模なデータセットを扱う際には、パフォーマンスの観点からSetオブジェクトの導入も検討すべきでしょう。
それぞれのメソッドが持つ返り値の特性や、NaNの扱い、オブジェクト比較時の挙動などを正しく理解することで、バグの少ない堅牢なコードを記述できるようになります。
まずは自分が扱っているデータ構造を確認し、今回の解説を参考に最適なメソッドを選んでみてください。
