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 或實際應用資料。
參考資料: