770 字
4 分鐘

indexOf 找不到 NaN?includes、空槽與索引 0 的判斷差別

本文目錄

[NaN].indexOf(NaN) 是 -1,[NaN].includes(NaN) 卻是 true。兩個方法採用不同的相等規則;只要知道值是否存在時,用 includes,需要位置時再選 indexOf 或條件式的 findIndex。

另一個常見錯誤是 if (items.indexOf(value)):找到第一筆會回傳 0,放進布林條件反而被當成 false。找不到的 -1 則會被當成 true。下面用斷言把這兩種錯誤和空槽行為分開看。

indexOf 回傳位置,includes 回傳布林值#

MDN indexOf 文件指出它使用嚴格相等比較,因此無法匹配 NaN;includes使用 SameValueZero,能把 NaN 視為相同值。兩者都不會把數字 1 和字串 '1' 合併。

將以下整段存成 .mjs,用 node 檔名.mjs 執行:

import assert from 'node:assert/strict';
const values = ['first', NaN, 1];
assert.equal(values.indexOf('first'), 0);
assert.equal(Boolean(values.indexOf('first')), false);
assert.equal(Boolean(values.indexOf('missing')), true);
assert.equal(values.indexOf('first') !== -1, true);
assert.equal(values.includes('missing'), false);
assert.equal(values.indexOf(NaN), -1);
assert.equal(values.includes(NaN), true);
assert.equal(values.findIndex(Number.isNaN), 1);
assert.equal(values.includes('1'), false);
assert.equal([-0].includes(+0), true);
const row = { id: 'a' };
assert.equal([row].includes(row), true);
assert.equal([row].includes({ id: 'a' }), false);
assert.equal([row].findIndex(item => item.id === 'a'), 0);
console.log('值、索引與參照檢查通過');

需要找 NaN 的位置時,findIndex(Number.isNaN) 直接表達條件;回傳結果仍要與 -1 比較。若用位置刪除資料,可接著看 splice 找不到 ID 時誤刪最後一筆的防護。

物件則依參照比較。內容相同的兩個物件仍各有身分,按 ID 搜尋要用明確條件;按 ID 去重則是另一個集合處理問題。

includes(undefined) 會把空槽納入判斷#

對一般陣列,indexOf 跳過不存在的索引,includes 則讀取它的值;沒有繼承值的空槽讀到 undefined。所以從 indexOf 改成 includes,可能連帶改變缺值判斷。

下面的診斷函式刻意只區分陣列自己的索引:一組是自有欄位缺席,一組是欄位存在但明確保存 undefined。它適合排查資料結構;最大長度是範例自訂的工作量限制。

import assert from 'node:assert/strict';
function inspectMissing(items) {
if (!Array.isArray(items)) throw new TypeError('需要陣列');
if (items.length > 10000) throw new RangeError('範例只處理一萬個位置');
const holes = [];
const explicitUndefined = [];
for (let index = 0; index < items.length; index += 1) {
if (!Object.hasOwn(items, index)) holes.push(index);
else if (items[index] === undefined) explicitUndefined.push(index);
}
return { holes, explicitUndefined };
}
const sparse = new Array(2);
sparse[1] = 'ready';
assert.equal(sparse.includes(undefined), true);
assert.equal(sparse.indexOf(undefined), -1);
assert.deepEqual(inspectMissing(sparse), {
holes: [0], explicitUndefined: [],
});
assert.equal([undefined].indexOf(undefined), 0);
assert.deepEqual(inspectMissing([undefined, 'ready']), {
holes: [], explicitUndefined: [0],
});
assert.deepEqual(inspectMissing([]), { holes: [], explicitUndefined: [] });
assert.throws(() => inspectMissing(null), TypeError);
assert.throws(() => inspectMissing(new Array(10001)), RangeError);
console.log('空槽與明確 undefined 檢查通過');

若原型鏈刻意放了數字索引,原生搜尋也可能讀到繼承的值。上述 helper 使用 Object.hasOwn,因此保留「自有欄位缺席」的判讀,與原生搜尋的目的不同。

要判斷每筆資料是否有效,還要檢查型別、允許值與是否允許空清單;includes(undefined) 只能回答某個值的搜尋結果。陣列初始化造成空槽的來源,可回到 new Array 與 Array.from處理。

本文兩個程式區塊由維護流程於 2026 年 11 月 6 日在 Node.js v24.13.0 執行通過。範例使用一般陣列,沒有涵蓋 Proxy、具副作用的 getter 或實際應用資料。

參考資料:

MDN:Array.prototype.indexOf()

MDN:Array.prototype.includes()

ECMAScript:Array 搜尋演算法

indexOf 找不到 NaN?includes、空槽與索引 0 的判斷差別
https://laplusda.com/posts/javascript-indexof-nan-includes-sparse-array/
作者
Zero
發佈於
2026-11-06
許可協議
CC BY-NC-SA 4.0