map(parseInt) 為什麼出現 NaN?JavaScript 進位參數與整數驗證
['1', '2', '3'].map(parseInt) 得到 [1, NaN, NaN],原因是 map 傳入的第二個參數 index,剛好成為 parseInt 的 radix(進位基底)。要逐筆解析十進位字串,改成 values.map(value => parseInt(value, 10))。
但修好 callback 參數,不代表輸入已經通過整數驗證。'12px' 仍能被解析成 12;表單數量、頁碼或資料 ID 應另外定義允許的格式。
逐筆攤開,就能看到 NaN 從哪裡來
map 的 callback收到 element, index, array;parseInt讀取字串與 radix。直接傳入函式時,JavaScript 不會替你刪掉第二個參數。
以下存成獨立 .mjs,以 Node.js 執行:
import assert from 'node:assert/strict';
assert.deepEqual(['1', '2', '3'].map(parseInt), [1, NaN, NaN]);assert.equal(parseInt('1', 0), 1); // 依字串判斷,這裡是十進位assert.ok(Number.isNaN(parseInt('2', 1))); // radix 1 無效assert.ok(Number.isNaN(parseInt('3', 2))); // 二進位沒有數字 3assert.deepEqual(['1', '2', '3'].map(value => parseInt(value, 10)), [1, 2, 3]);
// 不一定失敗:index 2 也可能讓字串被成功解讀成二進位。assert.deepEqual(['10', '10', '10'].map(parseInt), [10, NaN, 2]);console.log('callback 參數與 radix 檢查通過');第三筆回傳 2 的情況比 NaN 更難察覺。驗收時應比較每筆值,不能只檢查陣列長度或是否拋出例外。這和 sort 比較函式的回傳契約相似:函式名稱可以直接傳入,不代表它接受的參數契約適合該 callback。
Number 和 parseInt 解決的問題不同
若需求是完整的數字轉換,values.map(Number) 不會把 index 當成進位參數,但 Number('') 是 0,也接受小數與指數表示法。parseInt(value, 10) 則從開頭解析整數,遇到不屬於該進位的字元就停止。
因此 '12px' 可以拿來示範「擷取整數前綴」,卻不適合作為「有效商品數量」的驗證。也不要用 parseInt 取代所有小數運算或截斷需求。
要求整串都是十進位整數,先驗格式再轉值
下面定義一個明確契約:只接受字串、可有一個負號、不可有前後空白、加號、前導零、小數、指數或單位;允許 0,拒絕 -0,結果必須是安全整數。這是應用程式自行選擇的格式,不是 JavaScript 所有數字字面值的規則。
import assert from 'node:assert/strict';
function parseDecimalInteger(text) { if (typeof text !== 'string' || !/^(?:0|-?[1-9][0-9]*)$/.test(text)) { throw new TypeError('需要完整的十進位整數字串'); } const value = Number(text); if (!Number.isSafeInteger(value)) { throw new RangeError('超出安全整數範圍'); } return value;}
assert.equal(parseInt('12px', 10), 12);assert.equal(Number(''), 0);assert.equal(Number('1e2'), 100);assert.deepEqual(['0', '-12', '42'].map(parseDecimalInteger), [0, -12, 42]);for (const text of ['', ' 1', '1 ', '01', '+1', '-0', '1.2', '1e2', '12px', '1\n']) { assert.throws(() => parseDecimalInteger(text), TypeError);}assert.throws(() => parseDecimalInteger('9007199254740992'), RangeError);assert.throws(() => parseDecimalInteger(12), TypeError);console.log('整數格式與範圍檢查通過');若欄位只允許正整數,還要在解析後檢查 value > 0。若資料是超出安全範圍的識別碼,應保留字串或另設 BigInt 契約;數字已在 JSON 解析時失真,後面再轉字串也救不回來,可對照 JSON 大整數 ID 精度。
本文兩組範例由維護流程於 2026-11-02 在 Node.js v24.13.0 執行;沒有驗證實際表單、後端欄位或 API 資料。套用前先確定自己的欄位是否允許空白、前導零與負值,再調整契約和相對應的反例。
參考資料: