JavaScript `includes()` で要素・部分文字列の存在チェックを簡単にする方法

はい、承知いたしました。JavaScriptのincludes()メソッドについて、要素・部分文字列の存在チェックを簡単にする方法に焦点を当てた、詳細な説明を含む約5000語の記事を記述します。


JavaScript includes() で要素・部分文字列の存在チェックを簡単にする方法

JavaScript開発において、配列の中に特定の要素が含まれているか、あるいは文字列の中に特定の文字列(部分文字列)が含まれているかを確認する処理は非常に頻繁に登場します。このような「存在チェック」は、条件分岐、データ検証、検索機能など、様々な場面で不可欠です。

かつて、この存在チェックを行うには、配列の場合は indexOf() メソッドを使用し、その結果が -1 でないかを確認する必要がありました。文字列の場合も同様に indexOf() または search() メソッドを使用し、結果が -1 でないか、または null でないかを確認するのが一般的でした。しかし、これらの方法は直感的とは言えず、特に初心者にとってはコードの意図を瞬時に理解しづらい側面がありました。

そこで登場したのが、本記事の主題である includes() メソッドです。includes() メソッドは、配列や文字列が特定の要素または部分文字列を含んでいるかどうかを、よりシンプルかつ直感的に、そして可読性の高い方法で確認できるように設計されています。その名の通り、「〜を含むか?」という問いに直接的に答えるかのように、戻り値として真偽値(true または false)を返します。

本記事では、この includes() メソッドについて、その基本的な使い方から、配列、文字列、TypedArrayそれぞれにおける挙動、従来のメソッド (indexOf など) との比較、詳細な仕様、応用例、互換性、そして使用上の注意点に至るまで、徹底的に掘り下げて解説します。この記事を読むことで、includes() メソッドを完全に理解し、日々のコーディングで自信を持って使いこなせるようになることを目指します。

1. includes() メソッドとは何か?

includes() メソッドは、JavaScriptにおいて、あるコレクション(配列や文字列)が特定の要素や部分文字列を含んでいるかどうかを確認するための組み込みメソッドです。このメソッドは、チェック対象が見つかった場合に true を、見つからなかった場合に false を返します。真偽値を直接返すため、存在チェックのコードを非常に簡潔に記述することができます。

includes() メソッドは、主に以下のオブジェクトタイプで使用できます。

  • Array.prototype.includes(): 配列が特定の要素を含んでいるかチェックします。
  • String.prototype.includes(): 文字列が特定の文字列(部分文字列)を含んでいるかチェックします。
  • TypedArray.prototype.includes(): 型付き配列 (TypedArray) が特定の要素を含んでいるかチェックします。

これらのメソッドは、ECMAScript 2015 (ES6) で文字列版が、ECMAScript 2016 (ES7) で配列版が標準化されました。TypedArray版もES2016で追加されています。比較的モダンなJavaScript環境であれば、特別な対応なしに利用できます。

次に、それぞれのタイプの includes() メソッドについて、具体的な使い方を見ていきましょう。

2. Array.prototype.includes() の基本

配列の includes() メソッドは、指定された要素が配列内に存在するかどうかをチェックします。

構文

javascript
arr.includes(searchElement[, fromIndex])

  • arr: 存在チェックを行う対象の配列。
  • searchElement: 配列内で検索したい要素。
  • fromIndex (オプション): 検索を開始するインデックス。整数値を指定します。
    • この引数を省略した場合、検索はインデックス 0 から開始されます。
    • 負の値を指定した場合、そのインデックスは配列の末尾からのオフセットとして扱われます。具体的には、arr.length + fromIndex のインデックスから検索が開始されます。ただし、計算されたインデックスが 0 より小さい場合でも、検索はインデックス 0 から開始されます。
  • 戻り値: 指定された searchElement が配列内に存在する場合は true、存在しない場合は false を返します。

使用例

最も基本的な使い方は、検索したい要素だけを指定する方法です。

“`javascript
const fruits = [‘apple’, ‘banana’, ‘cherry’, ‘date’];

console.log(fruits.includes(‘banana’)); // true
console.log(fruits.includes(‘grape’)); // false
console.log(fruits.includes(‘Apple’)); // false (大文字・小文字を区別します)
“`

この例では、配列 fruits'banana' が含まれているか、'grape' が含まれているか、'Apple' が含まれているかを確認しています。結果として、'banana' は含まれているため true'grape''Apple' は含まれていないため false が返されています。文字列の場合、includes() は厳密に一致するかどうか(大文字・小文字を含めて)をチェックすることに注意してください。

次に、オプションの fromIndex 引数を使用する例を見てみましょう。

“`javascript
const numbers = [10, 20, 30, 40, 50, 20];

// インデックス 0 から検索を開始 (デフォルト)
console.log(numbers.includes(20)); // true

// インデックス 2 (30) から検索を開始
console.log(numbers.includes(20, 2)); // true (インデックス 5 の 20 が見つかる)

// インデックス 3 (40) から検索を開始
console.log(numbers.includes(20, 3)); // false (インデックス 3 以降に 20 は存在しない)

// 負のインデックスを指定: 最後の要素から数えて2番目 (-2) から検索を開始
// 配列の長さは 6 なので、インデックス 6 + (-2) = 4 (50) から検索開始
console.log(numbers.includes(20, -2)); // true (インデックス 5 の 20 が見つかる)

// 負のインデックスを指定: 配列の長さよりも絶対値が大きい負の値
// 配列の長さ 6。-10 を指定すると 6 + (-10) = -4。
// -4 は 0 より小さいので、インデックス 0 から検索開始として扱われる。
console.log(numbers.includes(10, -10)); // true (インデックス 0 の 10 が見つかる)
“`

fromIndex を適切に使うことで、配列の特定の部分だけを検索対象とすることができます。負の値の挙動も理解しておくと便利です。

厳密な比較 (Strict Equality Comparison)

Array.prototype.includes() は、検索する要素 (searchElement) と配列内の要素を比較する際に、Strict Equality Comparison Algorithm (厳密等価比較アルゴリズム) を使用します。これは、ほとんどの場合 === 演算子と同じ挙動をします。

この厳密比較にはいくつかの重要な点があります。

  1. 型の異なる値: 型が異なる値は常に false と判定されます。例えば、数値 10 と文字列 '10' は等価ではありません。

    javascript
    const mixed = [10, '20', 30];
    console.log(mixed.includes(10)); // true (数値 10 と 数値 10)
    console.log(mixed.includes('10')); // false (文字列 '10' は配列に含まれていない)
    console.log(mixed.includes(20)); // false (数値 20 は配列に含まれていない)
    console.log(mixed.includes('20')); // true (文字列 '20' と 文字列 '20')

  2. オブジェクト参照: オブジェクトや配列を比較する場合、それらが「同じ参照を持つオブジェクト」である場合にのみ true となります。内容が同じでも、異なるオブジェクトとして作成された場合は false です。

    “`javascript
    const obj1 = { id: 1 };
    const obj2 = { id: 2 };
    const obj3 = { id: 1 }; // 内容は obj1 と同じだが、異なるオブジェクト
    const objects = [obj1, obj2];

    console.log(objects.includes(obj1)); // true (同じ参照)
    console.log(objects.includes(obj3)); // false (異なる参照)
    console.log(objects.includes({ id: 1 })); // false (新しいオブジェクトリテラル)
    “`

  3. NaN の取り扱い: includes() メソッドの最も notable な特徴の一つは、NaN (Not-a-Number) を正しく検索できることです。これは、indexOf()=== 演算子では NaN === NaNfalse となるため、NaN を配列内で効率的に検索することが困難だった問題を解決します。

    “`javascript
    const listWithNaN = [1, 2, NaN, 4, 5];

    // includes() は NaN を見つけられる
    console.log(listWithNaN.includes(NaN)); // true

    // indexOf() は NaN を見つけられない (常に -1)
    console.log(listWithNaN.indexOf(NaN)); // -1

    // === 演算子でも NaN は NaN と等しくない
    console.log(NaN === NaN); // false
    “`

    この NaN を正しく扱える点は、数値計算の結果として NaN が発生しうる配列を扱う際に非常に有用です。

  4. +0 と -0: 厳密比較では、数値の +0-0 は等価とみなされます。

    javascript
    const signedZeros = [0, -0];
    console.log(signedZeros.includes(0)); // true (+0 が見つかる)
    console.log(signedZeros.includes(-0)); // true (-0 が見つかる)

Sparse Array (穴あき配列) の挙動

Sparse Array とは、特定のインデックスに要素が存在しない(undefined が意図的に代入されたのではなく、単にインデックスが飛ばされている)配列のことです。includes() メソッドは、このような Sparse Array の「穴」をどのように扱うのでしょうか。

includes() は、検索を行う際に実際に存在するインデックスのみをチェックします。もし検索対象の searchElementundefined であった場合、includes() は配列内に意図的に undefined が代入されているインデックスを探しますが、穴の部分は無視します。

“`javascript
const sparseArray = [1, , 3, undefined, 5]; // インデックス 1 は穴、インデックス 3 は undefined

console.log(sparseArray.includes(1)); // true (インデックス 0)
console.log(sparseArray.includes(3)); // true (インデックス 2)
console.log(sparseArray.includes(5)); // true (インデックス 4)

// 意図的に代入された undefined は見つかる
console.log(sparseArray.includes(undefined)); // true (インデックス 3)

// 穴の部分には undefined が存在しないとみなされるため、穴を探すことはできない
// includes() は穴を見つけられないため、存在しない要素としては扱われない
console.log(sparseArray.includes(sparseArray[1])); // false (sparseArray[1] は undefined だが、検索対象の undefined は「穴」ではないため見つからない)
“`

もし配列に undefined が含まれている可能性がある場合、includes(undefined) は意図的に undefined が代入された位置のみを検出します。穴の部分は含まれていないと見なされるため、注意が必要です。

3. String.prototype.includes() の基本

文字列の includes() メソッドは、指定された文字列(部分文字列)が別の文字列の中に含まれているかどうかをチェックします。

構文

javascript
str.includes(searchString[, position])

  • str: 存在チェックを行う対象の文字列。
  • searchString: str 内で検索したい部分文字列。
  • position (オプション): str 内で searchString の検索を開始するインデックス(位置)。整数値を指定します。
    • この引数を省略した場合、検索はインデックス 0 から開始されます。
    • 負の値を指定した場合も、内部的には 0 として扱われます。つまり、position 引数は負の値を受け付けますが、その振る舞いは常に position = Math.max(position, 0) と同じになります。負の値で検索開始位置を配列のように末尾から指定することはできません。
  • 戻り値: 指定された searchStringstr 内に見つかった場合は true、見つからなかった場合は false を返します。

使用例

“`javascript
const message = “Hello, world! Welcome to JavaScript.”;

console.log(message.includes(‘world’)); // true
console.log(message.includes(‘World’)); // false (大文字・小文字を区別する)
console.log(message.includes(‘JavaScript’)); // true
console.log(message.includes(‘python’)); // false
console.log(message.includes(”)); // true (空文字列は常に含まれるとみなされる)
“`

文字列版の includes() も Array版と同様に大文字・小文字を区別します。空文字列 ('') は、どのような文字列にも含まれていると見なされるため、常に true を返します。

オプションの position 引数を使用する例です。

“`javascript
const sentence = “The quick brown fox jumps over the lazy dog.”;

// インデックス 0 から検索を開始 (デフォルト)
console.log(sentence.includes(‘fox’)); // true

// インデックス 20 (jumps) から検索を開始
console.log(sentence.includes(‘fox’, 20)); // false (インデックス 20 以降には fox はない)

// インデックス 10 (brown) から検索を開始
console.log(sentence.includes(‘brown’, 10)); // true (インデックス 10 に brown が見つかる)

// 負の値は 0 として扱われる
console.log(sentence.includes(‘The’, -5)); // true (インデックス 0 から検索される)
“`

position は、文字列の先頭からのインデックスで指定します。負の値は 0 と同じ扱いになるため、Array版のような末尾からのオフセットとしては機能しないことに注意が必要です。

大文字・小文字の区別

String.prototype.includes() は、Array版と同様に厳密な比較を行います。特に文字列においては、大文字・小文字の区別が重要になります。

“`javascript
const programming = “JavaScript”;

console.log(programming.includes(‘script’)); // false
console.log(programming.includes(‘Script’)); // true
“`

もし大文字・小文字を区別せずに検索を行いたい場合は、検索対象の文字列と検索したい部分文字列の両方を、事前に toLowerCase() または toUpperCase() メソッドを使って同じケースに変換してから includes() を使う必要があります。

“`javascript
const programming = “JavaScript”;
const search = “script”;

// 大文字・小文字を区別しない検索
console.log(programming.toLowerCase().includes(search.toLowerCase())); // true
“`

このように前処理を行うことで、柔軟な検索が可能になります。

4. TypedArray.prototype.includes() の基本

TypedArray (型付き配列) は、バイナリデータの操作を効率的に行うための配列ライクなオブジェクトです。例えば、Int8Array, Uint8Array, Float32Array などがあります。これらのオブジェクトも、Array.prototype.includes() と同様の includes() メソッドを持っています。

構文

javascript
typedArray.includes(searchElement[, fromIndex])

  • typedArray: 存在チェックを行う対象の TypedArray オブジェクト。
  • searchElement: TypedArray 内で検索したい要素。TypedArray の要素型に合った値を指定するのが一般的ですが、異なる型の場合の挙動は TypedArray の種類と値によって異なります(後述)。
  • fromIndex (オプション): Array版と同様。検索を開始するインデックス。省略した場合 0。負の値は length + fromIndex として扱われます(計算結果が 0 より小さい場合は 0)。
  • 戻り値: 指定された searchElement が TypedArray 内に存在する場合は true、存在しない場合は false を返します。

使用例

“`javascript
const byteArray = new Uint8Array([10, 20, 30, 40, 20]);

console.log(byteArray.includes(20)); // true
console.log(byteArray.includes(50)); // false

// fromIndex を指定
console.log(byteArray.includes(20, 2)); // true (インデックス 4 の 20 が見つかる)
console.log(byteArray.includes(20, 3)); // false (インデックス 3 以降に 20 はない)
console.log(byteArray.includes(20, -2)); // true (インデックス 4 の 20 が見つかる)
“`

TypedArray 版の includes() は、Array 版とほぼ同様の使い勝手です。

厳密な比較と型強制

TypedArray 版の includes() も Array 版と同様に、要素の比較には Strict Equality Comparison Algorithm を使用します。しかし、TypedArray は厳密な型を持つため、検索要素 searchElement が TypedArray の要素型に変換される際の挙動に注意が必要です。

検索要素 searchElement は、TypedArray の要素型(例えば Uint8Float32)に内部的に型強制されてから比較が行われます。この型強制の挙動は、TypedArray のコンストラクタに値を渡した際の挙動と似ています。

例:Uint8Array (符号なし8ビット整数) の場合

“`javascript
const uint8 = new Uint8Array([100, 200, 50]);

// 数値 200 (Uint8 の範囲内)
console.log(uint8.includes(200)); // true

// 数値 255 (Uint8 の最大値)
console.log(uint8.includes(255)); // false

// 数値 300 (Uint8 の範囲外)。型強制で 300 & 0xFF = 44 になる。
// includes(300) は includes(44) と同じ意味になる。
console.log(uint8.includes(300)); // false (配列には 44 が含まれていない)

// 数値 -10 (Uint8 の範囲外)。型強制で -10 & 0xFF = 246 になる。
// includes(-10) は includes(246) と同じ意味になる。
console.log(uint8.includes(-10)); // false (配列には 246 が含まれていない)

// 文字列 ‘100’。数値 100 に変換されて比較。
console.log(uint8.includes(‘100’)); // true

// 文字列 ‘abc’。NaN に変換され、Uint8 では 0 になる。
// includes(‘abc’) は includes(0) と同じ意味になる。
console.log(uint8.includes(‘abc’)); // false (配列には 0 が含まれていない)
“`

このように、TypedArray の includes() では、検索要素がその TypedArray の要素型にどのように型強制されるかを理解しておくことが重要です。特に数値の範囲外の値や、数値に変換できない文字列などを検索する場合に予期しない結果になる可能性があります。

NaN の取り扱いについては、Array 版と同様に NaN を検索できます。

javascript
const floatArray = new Float32Array([1.2, 3.4, NaN, 5.6]);
console.log(floatArray.includes(NaN)); // true

ただし、Int 系の TypedArray (Int8Array, Uint8Array など) の要素型は整数であるため、これらの配列に NaN は含まれません。

5. includes() と従来のメソッドの比較

includes() メソッドの大きな利点は、そのシンプルさと直感的な戻り値 (true/false) です。ここでは、これまで存在チェックに使われてきた従来のメソッド (indexOf, search, test) と includes() を比較し、それぞれの違いと使い分けについて解説します。

includes() vs indexOf() (Array)

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

比較ポイント:

  • 戻り値:
    • includes(): boolean (true または false)
    • indexOf(): number (インデックス、または -1)
  • 目的:
    • includes(): 要素の存在チェック に特化。
    • indexOf(): 要素のインデックス検索。存在チェックにも使えるが、主要な目的ではない。
  • 可読性:
    • includes(): array.includes(element) は「配列が要素を含むか?」と直接的に読めるため、存在チェックの意図が明確で可読性が高い。
    • indexOf(): array.indexOf(element) !== -1 は「要素のインデックスが -1 ではないか?」と読む必要があり、存在チェックの意図を理解するのに一瞬考える必要がある。
  • NaN の取り扱い:
    • includes(): NaN を正しく検索できる。
    • indexOf(): NaN を検索できない (NaN === NaNfalse のため、常に -1 を返す)。
  • パフォーマンス:
    • 両者とも、要素が見つかった時点で検索を終了する最適化が行われる可能性があります。存在チェックだけが目的なら、理論上 includes() の方がわずかに効率が良い可能性がありますが、実用上大きな差が出ないことがほとんどです。インデックスが必要な場合は indexOf() を使う必要があります。
  • 比較方法:
    • includes(): Strict Equality Comparison Algorithm (=== と同等、ただし NaN の扱いは異なる)。
    • indexOf(): Strict Equality Comparison Algorithm (=== と同等)。

コード例による比較:

存在チェックの場合:

“`javascript
const colors = [‘red’, ‘green’, ‘blue’];

// includes() を使用
const hasGreen_includes = colors.includes(‘green’); // true
const hasYellow_includes = colors.includes(‘yellow’); // false
console.log(includes(): hasGreen=${hasGreen_includes}, hasYellow=${hasYellow_yellow});

// indexOf() を使用 (従来のやり方)
const hasGreen_indexOf = colors.indexOf(‘green’) !== -1; // true (インデックス 1 !== -1)
const hasYellow_indexOf = colors.indexOf(‘yellow’) !== -1; // false (インデックス -1 !== -1 は false)
console.log(indexOf(): hasGreen=${hasGreen_indexOf}, hasYellow=${hasYellow_indexOf});
“`

存在チェックが目的であれば、includes() を使用した方がコードが簡潔で意図が明確です。

NaN 検索の場合:

“`javascript
const numbers = [1, NaN, 3];

// includes() を使用
console.log(numbers.includes(NaN)); // true

// indexOf() を使用
console.log(numbers.indexOf(NaN)); // -1
“`

NaN の存在チェックが必要な場合は、includes() が唯一の方法です。

まとめ:

  • 要素が存在するかどうかだけを知りたい場合: includes() が最適です。可読性が高く、NaN も検索できます。
  • 要素が最初に見つかったインデックスを知りたい場合: indexOf() を使用する必要があります。

includes() vs indexOf() (String)

String.prototype.indexOf() は、指定された部分文字列が文字列内で最初に見つかったインデックスを返します。見つからなかった場合は -1 を返します。

比較ポイント:

  • 戻り値: String 版も Array 版と同様。includes()booleanindexOf()number
  • 目的: String 版も Array 版と同様。includes() は存在チェック、indexOf() はインデックス検索。
  • 可読性: String 版も Array 版と同様。str.includes(sub) は直感的。str.indexOf(sub) !== -1 は迂遠。
  • 大文字・小文字の区別: 両者ともデフォルトで大文字・小文字を区別します。
  • 空文字列の取り扱い: 両者とも空文字列は常に含まれるとみなします。
  • パフォーマンス: Array 版と同様、存在チェックのみなら includes() がわずかに効率的な可能性がありますが、実用上は大きな差が出にくいです。
  • 比較方法: 両者とも厳密な文字列比較を行います。

コード例による比較:

“`javascript
const sentence = “The quick brown fox.”;

// includes() を使用
console.log(sentence.includes(‘quick’)); // true
console.log(sentence.includes(‘slow’)); // false

// indexOf() を使用
console.log(sentence.indexOf(‘quick’) !== -1); // true (インデックス 4 !== -1)
console.log(sentence.indexOf(‘slow’) !== -1); // false (インデックス -1 !== -1 は false)
“`

文字列の場合も、部分文字列の存在チェックが目的であれば includes() が推奨されます。

まとめ:

  • 部分文字列が存在するかどうかだけを知りたい場合: includes() が最適です。
  • 部分文字列が最初に見つかったインデックスを知りたい場合: indexOf() を使用する必要があります。

includes() vs search() / test() (String with Regular Expression)

JavaScriptには、正規表現を使って文字列を検索する方法もあります。主に String.prototype.search() メソッド(正規表現オブジェクトを引数にとる)と RegExp.prototype.test() メソッド(正規表現オブジェクトに対して文字列を引数にとる)がこれにあたります。

比較ポイント:

  • 検索方法:
    • includes(): リテラル文字列 のみ。厳密な部分文字列一致。
    • search() / test(): 正規表現パターン によるマッチング。より複雑なパターン検索が可能。
  • 戻り値:
    • includes(): boolean
    • search(): number (マッチした位置のインデックス、または -1)
    • test(): boolean
  • 目的:
    • includes(): 単純な部分文字列の存在チェック。
    • search() / test(): 複雑なパターンに一致する部分の存在チェック、またはその位置の特定。
  • 柔軟性:
    • includes(): 固定文字列しか検索できない。大文字・小文字を区別しない検索には前処理が必要。
    • search() / test(): フラグ (i で大文字・小文字を区別しないなど) や特別なメタ文字を使って、柔軟かつ強力なパターン検索ができる。

コード例による比較:

“`javascript
const url = “https://example.com/users/123/profile”;

// 特定の単純な部分文字列が含まれるか?
console.log(url.includes(‘/users/’)); // true
console.log(url.includes(‘/posts/’)); // false

// 数字だけのIDが含まれるか? (正規表現が必要)
// search() を使用
console.log(url.search(/\/\d+\//) !== -1); // true (例: /123/ にマッチ)
// test() を使用
console.log(/\/\d+\//.test(url)); // true

// 大文字・小文字を区別しない検索 (例: .COM)
console.log(url.includes(‘.COM’)); // false
console.log(url.toLowerCase().includes(‘.com’)); // true

// 正規表現 (iフラグ) を使用
console.log(/.com/i.test(url)); // true
“`

まとめ:

  • 単純な固定文字列の存在チェック: includes() が最適です。最もシンプルでパフォーマンスも優れています。
  • 大文字・小文字を区別しない固定文字列の検索: includes()toLowerCase() などの組み合わせ。
  • 複雑なパターン、特定の形式、または大文字・小文字を区別しない検索などが含まれる場合: search()test() (正規表現) を使用する必要があります。

6. includes() メソッドの詳細な挙動

ここでは、これまでに触れてきた includes() の挙動について、より技術的な詳細を掘り下げます。

厳密等価比較 (Strict Equality Comparison) の詳細

Array.prototype.includes() および TypedArray.prototype.includes() は、仕様上 SameValueZero Comparison と呼ばれるアルゴリズムを使用して要素を比較します。これは、ほぼ === と同じですが、NaNNaN が等価とみなされる点が === と異なります。

“`javascript
// SameValueZero 比較
NaN == NaN // true (includes() はこのルールを使う)
0 == -0 // true (includes() はこのルールを使う)

// Strict Equality 比較 (===)
NaN === NaN // false (indexOf() はこのルールを使う)
0 === -0 // true (indexOf() もこのルールを使う)
“`

この SameValueZero 比較を採用しているため、Array.prototype.includes() および TypedArray.prototype.includes() では NaN を検索できるのです。String.prototype.includes() は要素の比較ではなく部分文字列のマッチングを行うため、この SameValueZero の話は該当しません。

fromIndex / position の詳細な解釈

fromIndex (Array, TypedArray) および position (String) 引数の解釈について、より詳しく見てみます。これらの引数は、検索を開始する「仮想的な」インデックスを指定します。

Array.prototype.includes(searchElement[, fromIndex])

  1. fromIndex が省略された場合、または undefined の場合、検索はインデックス 0 から開始されます。
  2. fromIndex が整数に変換されます。浮動小数点数は切り捨てられます。
  3. 変換されたインデックス from が計算されます。
    • from が負の値の場合: arr.length + from が計算上の開始インデックスとなります。
    • from が負でない場合: from がそのまま開始インデックスとなります。
  4. 実際の検索は、計算された開始インデックスから配列の末尾 (arr.length - 1) まで順に行われます。
  5. ただし、計算された開始インデックスが arr.length 以上の場合、検索範囲は空になり、要素は見つからないため false が返されます。
  6. 計算された開始インデックスが 0 より小さい場合(負の値を指定して arr.length + fromIndex が負になった場合など)、検索はインデックス 0 から開始されます。

例:arr = [a, b, c, d, e], length = 5

  • fromIndex = 2: インデックス 2 (c) から開始。
  • fromIndex = -2: 5 + (-2) = 3。インデックス 3 (d) から開始。
  • fromIndex = -10: 5 + (-10) = -50 より小さいのでインデックス 0 (a) から開始。
  • fromIndex = 5: 5length と同じ。検索範囲は空。false
  • fromIndex = 10: 10length より大きい。検索範囲は空。false

String.prototype.includes(searchString[, position])

  1. position が省略された場合、または undefined の場合、検索はインデックス 0 から開始されます。
  2. position が整数に変換されます。浮動小数点数は切り捨てられます。
  3. 変換されたインデックス pos が計算されます。
    • pos が負の値の場合、pos0 として扱われます。
    • pos が負でない場合、pos がそのまま開始インデックスとなります。
  4. 実際の検索は、計算された開始インデックスから文字列の末尾 (str.length - searchString.length) まで行われます。searchString の長さも考慮されるため、検索開始位置が文字列の末尾に近すぎると、searchString 全体が収まる余地がないため見つかりません。
  5. 計算された開始インデックスが str.length 以上の場合でも、エラーにはならず、検索範囲に応じて結果が決定されます。

例:str = "abcdef", length = 6, searchString = "cd", searchLength = 2

  • position = 2: インデックス 2 (c) から開始。str[2] から “cd” を検索。見つかる (true)。
  • position = -2: 負の値なので 0 として扱われる。インデックス 0 (a) から開始。str[0] から “cd” を検索。見つかる (true)。
  • position = 5: インデックス 5 (f) から開始。str.length - searchString.length = 6 - 2 = 4。検索開始位置は 5 なので、検索範囲はインデックス 4 以降になりますが、searchString (“cd”) の長さが 2 あるため、インデックス 5 から開始すると “cd” 全体を収める場所がありません。見つからない (false)。
  • position = 4: インデックス 4 (e) から開始。str.length - searchString.length = 4。検索範囲はインデックス 4 以降。str[4] から “cd” を検索。見つからない (false)。

String 版の position は、Array 版の fromIndex とは異なり、負の値が常に 0 として扱われる点、そして検索開始位置だけでなく searchString の長さも暗黙的に考慮される点に注意が必要です。

パフォーマンスに関する考慮事項

一般的に、includes() メソッドは単純な線形探索(配列や文字列の先頭から順に見ていく)アルゴリズムで実装されています。

  • 最良のケース: 検索対象の要素/部分文字列が配列/文字列の先頭付近で見つかる場合、検索はすぐに終了するため非常に高速です。
  • 最悪のケース: 検索対象の要素/部分文字列が配列/文字列の末尾付近に存在するか、あるいは全く存在しない場合、配列/文字列全体をほぼ走査する必要があるため、処理時間は配列/文字列の長さに比例します(O(n))。

ほとんどの一般的なユースケースでは、includes() のパフォーマンスは十分です。しかし、非常に大規模な配列や文字列に対して、同じ要素/部分文字列の存在チェックを繰り返し行う場合 などは、パフォーマンスが問題になる可能性があります。

そのようなケースでは、代替手段として以下を検討できます。

  • Set または Map を使う: 配列に含まれるユニークな要素の存在チェックを高速化したい場合、事前に Set や Map を作成しておくと、存在チェックが O(1) の計算量で行えるようになります。

    “`javascript
    const largeArray = / 非常に大きな配列 /;
    const uniqueElements = new Set(largeArray); // Setの作成には O(n) かかる

    // 存在チェックは高速 (O(1))
    console.log(uniqueElements.has(searchElement));
    ``
    ただし、Set/Map の作成には初期コストがかかるため、一度だけチェックする場合や要素の種類が多い場合は
    includes()` の方が効率的なこともあります。繰り返しチェックする場合に有効な最適化手法です。

  • 文字列検索アルゴリズム: 文字列に対して非常に高度なパターンマッチングや超高速な検索が必要な場合は、より洗練された検索アルゴリズム(例:KMP法、Boyer-Moore法など)を自分で実装するか、それを利用するライブラリを検討することもあります。しかし、JavaScriptの組み込みメソッドは十分に最適化されているため、ほとんどのケースでは includes() や正規表現で十分です。

7. includes() メソッドの応用例

includes() メソッドはそのシンプルさゆえに、様々な場面で応用できます。いくつか例を見てみましょう。

複数の条件のいずれかに一致するかどうかのチェック

ある要素が、許可された値のリストに含まれているか確認したい場合などに便利です。

“`javascript
function processRequest(status) {
const allowedStatuses = [‘pending’, ‘processing’, ‘completed’];

// 許可されたステータスのいずれかか?
if (allowedStatuses.includes(status)) {
console.log(Status "${status}" is valid. Processing...);
// 実際の処理…
} else {
console.warn(Status "${status}" is invalid.);
// エラー処理…
}
}

processRequest(‘pending’); // Status “pending” is valid. Processing…
processRequest(‘cancelled’); // Status “cancelled” is invalid.
“`

特定の要素を除外する処理 (filter と組み合わせて)

配列から特定の要素(または要素のリスト)を除外したい場合、includes()filter() を組み合わせることができます。

“`javascript
const allItems = [1, 2, 3, 4, 5, 6, 7, 8, 9];
const itemsToRemove = [3, 5, 7];

// 削除リストに含まれていない要素だけを抽出
const filteredItems = allItems.filter(item => !itemsToRemove.includes(item));

console.log(filteredItems); // [ 1, 2, 4, 6, 8, 9 ]
“`

入力値検証

ユーザーからの入力が、事前に定義された有効な値のリストに含まれているか確認する際に役立ちます。

“`javascript
const userInput = ‘medium’;
const validSizes = [‘small’, ‘medium’, ‘large’];

if (validSizes.includes(userInput)) {
console.log(Selected size: ${userInput});
} else {
console.error(Invalid size: ${userInput}. Please choose from ${validSizes.join(', ')}.);
}
“`

文字列ベースのルーターや条件分岐

URLパスや特定の文字列パターンに基づいて処理を分岐させる際に、正規表現ほど複雑ではない場合に includes() を使えます。

“`javascript
const currentPath = ‘/dashboard/settings/account’;

if (currentPath.includes(‘/dashboard/’)) {
console.log(‘Navigated to a dashboard section.’);
}

if (currentPath.includes(‘/settings/’)) {
console.log(‘Navigated to a settings page.’);
}

if (currentPath.includes(‘/admin/’)) {
console.log(‘Navigated to an admin section.’); // この例では実行されない
}
“`

スイッチ文の代替または補助

複数の文字列に対して同じ処理を行いたい場合に、switch 文の各 case に書く代わりに、対象文字列がリストに含まれているか includes() でチェックし、一つの分岐にまとめることでコードを簡潔にできます。

“`javascript
const day = ‘Saturday’;

const weekendDays = [‘Saturday’, ‘Sunday’];
const weekdayDays = [‘Monday’, ‘Tuesday’, ‘Wednesday’, ‘Thursday’, ‘Friday’];

if (weekendDays.includes(day)) {
console.log(‘It is the weekend!’);
} else if (weekdayDays.includes(day)) {
console.log(‘It is a weekday.’);
} else {
console.log(‘Invalid day.’);
}

// これは以下の switch 文の代替になります
// switch (day) {
// case ‘Saturday’:
// case ‘Sunday’:
// console.log(‘It is the weekend!’);
// break;
// case ‘Monday’:
// case ‘Tuesday’:
// case ‘Wednesday’:
// case ‘Thursday’:
// case ‘Friday’:
// console.log(‘It is a weekday.’);
// break;
// default:
// console.log(‘Invalid day.’);
// }
“`

リストが短く、各ケースで全く同じ処理を行う場合は、includes() を使う方がコードが短く済むことがあります。

8. includes() メソッドの互換性 (Compatibility)

includes() メソッドは、ECMAScript 2015 (ES6) で String 版が、ECMAScript 2016 (ES7) で Array 版と TypedArray 版が導入されました。比較的新しいメソッドであるため、特に古いブラウザやJavaScript実行環境での互換性に注意が必要です。

  • String.prototype.includes(): Internet Explorer (IE) はサポートしていません。Edge 12以降、Firefox 17以降、Chrome 41以降などでサポートされています。
  • Array.prototype.includes(): IE はサポートしていません。Edge 13以降、Firefox 43以降、Chrome 47以降などでサポートされています。
  • TypedArray.prototype.includes(): TypedArray 自体がES6以降の機能であるため、比較的モダンな環境でサポートされています。

現在主流のモダンブラウザ(Chrome, Firefox, Safari, Edgeの新しいバージョンなど)やNode.js環境では、includes() メソッドは広くサポートされています。しかし、ターゲットとするユーザー層や実行環境に古いブラウザ (特にIE) が含まれる場合は、互換性のために Polyfill を適用する必要があります。

Polyfill は、新しいJavaScript機能をサポートしていない古い環境で、その機能と同等の挙動を提供するコードのことです。Babelなどのトランスパイラや、core-jsのようなライブラリを使用することで、自動的に必要な Polyfill を含めることができます。

例えば、Array.prototype.includes() の Polyfill は以下のようになります(簡略化された例)。

“`javascript
// Array.prototype.includes の簡易 Polyfill
if (!Array.prototype.includes) {
Array.prototype.includes = function(searchElement /, fromIndex/) {
‘use strict’;
if (this == null) {
throw new TypeError(‘”this” is null or not defined’);
}

var O = Object(this);
var len = O.length >>> 0;
if (len === 0) {
  return false;
}
var n = arguments[1] | 0; // fromIndex
var k = Math.max(n >= 0 ? n : len - Math.abs(n), 0); // 実際の開始インデックス

while (k < len) {
  // SameValueZero 比較 (NaN == NaN が true)
  if (SameValueZero(O[k], searchElement)) {
    return true;
  }
  k++;
}
return false;

};

// SameValueZero 関数の定義が必要 (例: Object.is に似ているが -0 と +0 は等価)
function SameValueZero(x, y) {
return x === y || (typeof x === ‘number’ && typeof y === ‘number’ && isNaN(x) && isNaN(y));
}
}
“`

この Polyfill は、Array.prototype.includes が存在しない環境で includes() メソッドを追加します。SameValueZero 比較を正しく実装する必要がある点、および this の扱い(厳格モードなど)に注意が必要です。実務では、自分で実装するよりも core-js のような十分にテストされたライブラリを使用するのが一般的です。

モダンな開発では、Babelと適切なプリセット(例:@babel/preset-env with useBuiltIns: 'usage')を使用すれば、ターゲット環境に必要な Polyfill が自動的に含まれるため、開発者が手動で気にする必要はほとんどありません。しかし、includes() が比較的新しいメソッドであり、古い環境では注意が必要であることを理解しておくことは重要です。

9. includes() 使用時の注意点

includes() メソッドはそのシンプルさゆえに扱いやすいですが、意図しない挙動を避けるためにいくつかの注意点があります。

  1. 厳密比較であること: Array 版と TypedArray 版は SameValueZero 比較を使用し、String 版は厳密な文字列一致を使用します。数値 10 と文字列 '10' は異なるものとして扱われます。オブジェクトは内容ではなく参照で比較されます。特にオブジェクトを含む配列で特定の条件を満たすオブジェクトの存在を確認したい場合は、includes() は使えません。代わりに some() メソッドなどを使用する必要があります。

    “`javascript
    const users = [{ id: 1, name: ‘Alice’ }, { id: 2, name: ‘Bob’ }];
    const searchUser = { id: 1, name: ‘Alice’ };

    // 同じ内容でも参照が異なるため false
    console.log(users.includes(searchUser)); // false

    // IDが1のユーザーが存在するかは some() で確認
    console.log(users.some(user => user.id === 1)); // true
    “`

  2. 大文字・小文字の区別 (文字列版): String.prototype.includes() はデフォルトで大文字・小文字を区別します。区別しない検索が必要な場合は、事前に toLowerCase()toUpperCase() で変換してください。

  3. fromIndex / position の負の値の挙動: Array/TypedArray 版の fromIndex は負の値で末尾からのオフセットを指定できますが、String 版の position は負の値が常に 0 として扱われます。これらの違いを理解しておく必要があります。

  4. Sparse Array の挙動: Array 版の includes() は Sparse Array の「穴」をスキップします。undefined を検索した場合は、意図的に undefined が代入された要素のみを検出します。

  5. NaN の扱いは Array/TypedArray のみ: NaN を正しく検索できるのは Array.prototype.includes() および TypedArray.prototype.includes() です。String.prototype.includes() に NaN を渡した場合、文字列 'NaN' が検索されます。

これらの注意点を踏まえ、用途に応じて適切なメソッドを選択することが重要です。単純な要素/部分文字列の存在確認であれば includes() が第一選択肢となりますが、より複雑な条件での検索や、オブジェクトの内容に基づく検索などには、some(), find(), filter() などの他の配列メソッドや、正規表現、あるいは Set/Map などのデータ構造を検討する必要があります。

10. まとめ

本記事では、JavaScriptの includes() メソッドについて、配列、文字列、TypedArray それぞれにおける基本的な使い方、詳細な挙動、そして従来のメソッド (indexOf, search, test) との比較を中心に解説しました。

includes() メソッドの最大の利点は、そのシンプルさと可読性の高さにあります。「AはBを含むか?」という問いに対して、真偽値 (true / false) で直接的に答えるその設計は、存在チェックという頻繁に行われる処理のコードを非常に直感的で分かりやすいものに変えてくれます。

特に、Array.prototype.includes() が NaN を正しく検索できる点は、indexOf() にはない大きな利点です。文字列版も、単純な部分文字列の存在確認であれば indexOf() !== -1 という記述よりも includes() の方が自然です。

もちろん、includes() が万能というわけではありません。要素/部分文字列が見つかった「位置」が必要な場合は indexOf()search() が必要ですし、複雑なパターンマッチングには正規表現を使用する必要があります。また、オブジェクトの内容に基づく検索には some() などの別のメソッドが適しています。

重要なのは、JavaScriptが提供する様々なメソッドの特性を理解し、解決したい問題に対して最も適切で、かつコードの意読性が高まるメソッドを選択することです。includes() メソッドは、多くの一般的な存在チェックのシナリオにおいて、この「適切で意読性の高い選択肢」となり得ます。

モダンなJavaScript開発において、includes() は要素・部分文字列の存在チェックを簡単にするための強力かつ必須のツールと言えるでしょう。古い環境への対応が必要な場合は Polyfill を適切に管理しつつ、積極的に活用していくことをお勧めします。

この記事を通じて、includes() メソッドの理解が深まり、日々のコーディング作業がより効率的で快適なものになれば幸いです。

参考文献


コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

上部へスクロール