在 JavaScript 中,我們經常需要檢查一個字串是否包含某個特定的字元或子字串。本文比較三種常用方法:indexOf()includes()filter(),涵蓋語法、可讀性、相容性與效能差異。

1. indexOf() 方法

indexOf() 是最古老且支援度最廣的方法之一。

語法

1
string.indexOf(searchValue[, fromIndex])

用法

1
2
3
const str = "Hello, World!";
const hasO = str.indexOf('o') !== -1; // true
const hasZ = str.indexOf('z') !== -1; // false

優點

  • 相容性好,支援舊版瀏覽器
  • 可以指定開始搜尋的位置

缺點

  • 返回值不是布林型,需要與 -1 比較
  • 程式碼可讀性較差

2. includes() 方法

includes() 是 ES6 引入的方法,專門用於檢查字串是否包含特定子字串。

語法

1
string.includes(searchString[, position])

用法

1
2
3
const str = "Hello, World!";
const hasO = str.includes('o'); // true
const hasZ = str.includes('z'); // false

優點

  • 直接返回布林值,更直觀
  • 程式碼更簡潔,可讀性強
  • 可以指定開始搜尋的位置

缺點

  • 不支援正規表達式
  • 在舊版瀏覽器中可能需要 polyfill

3. filter() 方法

filter() 是陣列方法,可以搭配 Array.from() 將字串拆成字元陣列後進行篩選。不過要注意:這種寫法只適合單一字元比對,無法用來搜尋多字元子字串(例如 'World''lo')。若要搜尋子字串,仍須搭配 includes()indexOf()

語法

1
2
// 注意:此語法只能逐字元比對,不能搜尋多字元子字串
Array.from(string).filter(char => char === searchChar).length > 0

用法

1
2
3
4
5
6
7
const str = "Hello, World!";
// ✅ 可用:搜尋單一字元 'o'
const hasO = Array.from(str).filter(char => char === 'o').length > 0; // true
const hasZ = Array.from(str).filter(char => char === 'z').length > 0; // false

// ❌ 錯誤用法:搜尋子字串 'World' 時永遠回傳 false,因為每個 char 只有一個字元
const hasWorld = Array.from(str).filter(char => char === 'World').length > 0; // false(錯誤!)

優點

  • 靈活性高,可以對字元套用複雜的條件篩選
  • 可以同時篩選多種字元(搭配陣列或 || 條件)

缺點

  • 只能比對單一字元,無法直接搜尋多字元子字串
  • 語法較為複雜
  • 效能較差,特別是對於長字串;若只需判斷是否存在,應改用 Array.from(str).some(c => c === searchChar),短路後即停、效能遠勝 filter

效能比較

為了比較這三種方法的效能,可以進行一個簡單的基準測試:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const str = "Hello, World!".repeat(1000000); // 建立一個長字串
const searchChar = '!';
let result; // 用變數承接結果,避免 JS 引擎 dead-code elimination

console.time('indexOf');
for (let i = 0; i < 100; i++) {
result = str.indexOf(searchChar) !== -1;
}
console.timeEnd('indexOf');

console.time('includes');
for (let i = 0; i < 100; i++) {
result = str.includes(searchChar);
}
console.timeEnd('includes');

console.time('filter');
for (let i = 0; i < 100; i++) {
result = Array.from(str).filter(char => char === searchChar).length > 0;
}
console.timeEnd('filter');

在單機測試中得到以下數字(僅供方向參考,實際數值因 JS 引擎版本、硬體與測試環境差異顯著):

  • indexOf: 約 5ms
  • includes: 約 7ms
  • filter: 約 15000ms

兩者 2ms 的差距落在量測雜訊範圍,不代表 indexOf 系統性優於 includesfilter 慢上千倍這個方向是可重現的結論。

結論

  1. 效能indexOf()includes() 在現代 JavaScript 引擎(V8、SpiderMonkey)上效能幾乎相同,差距落在量測雜訊範圍內,不構成選用依據。filter 慢上千倍的差距在任何字串長度下都會出現,選用時必須考慮。
  2. 可讀性includes() 最清晰直觀。
  3. 靈活性filter() 可套用複雜條件,但若只需判斷存在與否,some() 是更正確的替代(短路停止,不掃完整個陣列)。

選用建議

  • 一般字元或子字串搜尋,用 includes()
  • 需相容舊版瀏覽器,或需要取得字元的位置索引,用 indexOf()
  • 需複雜條件篩選且可接受只比對單一字元,優先考慮 some();只有需要收集所有符合字元時才用 filter()
  • ES6 起也有 startsWith()endsWith() 可直接判斷前後綴,不必用 indexOf 配合位置 0 來繞。

參考資源