JavaScript map(parseInt):同样是三个数字字符串,为什么后两项变成 NaN

前天 3阅读

把字符串数组转成整数,看起来只需 values.map(parseInt)。输入一、二、三个普通十进制字符串,结果却是一个正确数字加两个 NaN。parseInt 本身并没有随机失败,而是 map 会给回调传入元素、下标和原数组,第二个参数恰好被 parseInt 当成了进制。

函数能被调用,不代表它与回调接口的参数含义相容。下面保留三次调用的参数,再写一个只转发所需实参的适配函数。保存为 demo.mjs,用 node demo.mjs 运行。实验仅处理内存数组,输出中会把 NaN 显式写成文字,避免 JSON 序列化改变观察结果。

展开回调调用,错误就不再神秘

JavaScript map(parseInt):同样是三个数字字符串,为什么后两项变成 NaN

AI概念示意图:回调收到多路参数,适配器只把需要的数据送入转换入口。图片不是运行截图。

import assert from "node:assert/strict";

const input = ["1", "2", "3"];
const wrong = input.map(parseInt);
assert.equal(wrong[0], 1);
assert.ok(Number.isNaN(wrong[1]));
assert.ok(Number.isNaN(wrong[2]));
console.log("direct callback:", wrong.map(x => Number.isNaN(x) ? "NaN" : String(x)));

const seen = [];
input.map((value, index, source) => {
  seen.push([value, index, source === input]);
  return parseInt(value, index);
});
assert.deepEqual(seen, [["1", 0, true], ["2", 1, true], ["3", 2, true]]);
console.log("callback arguments:", seen);

const fixed = input.map(value => parseInt(value, 10));
assert.deepEqual(fixed, [1, 2, 3]);
assert.deepEqual(Array.from(input, value => parseInt(value, 10)), fixed);
assert.equal(["10", "10", "10"].map(parseInt)[2], 2);
assert.deepEqual(input.map(Number), [1, 2, 3]);
assert.equal(Number(""), 0);
assert.equal(parseInt("12px", 10), 12);
assert.ok(Number.isNaN(Number("12px")));
let calls = 0;
assert.deepEqual([].map(value => { calls++; return value; }), []);
assert.equal(calls, 0);
console.log("explicit radix:", fixed);
console.log("third binary parse:", ["10", "10", "10"].map(parseInt)[2]);

第一项等价于 parseInt("1", 0),零进制参数采用函数的推断规则,因此这里得到一。第二项等价于 parseInt("2", 1),一不在允许的进制范围中,得到 NaN。第三项使用二进制读取字符串三,开头就不是合法二进制数字,因此也得到 NaN。

seen 记录证明 map 还提供第三个参数,也就是原数组引用;这个参数对 parseInt 没有用途。这里的关键是第二个位置发生了含义冲突,并非“回调只能接收一个参数”。很多回调恰好需要下标,应按实际接口保留或丢弃相应参数。

用一层小函数明确参数角色

value => parseInt(value, 10) 把当前元素送到文本位置,并把进制固定为十。下标仍由 map 提供给这层箭头函数,但没有被继续传入解析器。修复后结果是一、二、三。代码也把同样做法用于 Array.from 的映射函数,说明需要检查的是回调约定,而不只是某一个数组方法。

如果已有函数确实只接受一个业务参数,也可以用 value => convert(value) 包一层,阻止额外参数改变它的可选行为。写法稍长,却把适配关系留在调用点。不要仅凭函数名都像“转换器”,就假定它们能互相替代或直接作为任意回调。

修好参数错位,还要检查选择的转换规则

另一组样本把 10 放在第三项,错误写法会按二进制得到二,结果是普通整数而非 NaN。只过滤 NaN 因而不能找齐受影响的数据。遇到这类故障应使用已知输入核对每一项的预期值,并检查历史结果是否在后续步骤被错误当作合法数字。

Number 作为 map 回调不会把下标当进制,示例也能得到一、二、三,但它的转换规则与 parseInt 不相同。比如前者把空字符串变成零,后者能从带单位文字的开头读取整数。应根据输入契约选函数,不能把换成 Number 当成所有解析场景的统一补丁。

把接口适配与业务校验分开验收

本例没有实现完整数字表单校验,也不接受任意进制文本。若需求是整段合法的十进制整数,应另外检查文本格式和数值范围,再做转换。避免在已经错位解析之后,通过默认补零让数组长度看起来正确,却永久丢失原始错误。

给这种适配写测试时,至少包含下标零、一、二,以及第三项能够被二进制成功读出的文本。还应保留一个空数组确认回调不会运行。通过记录回调实际收到的参数,再核对被调用函数的签名,可以把同类错误定位到具体参数位置,无需猜测引擎是否处理了错误字符。

资料核对日期:2026年10月2日(北京时间)。示例在 Node.js 24.19.0 中独立运行。

参考资料

ECMAScript 规范:Array.prototype.map

ECMAScript 规范:parseInt 参数处理

文章版权声明:除非注明,否则均为云鹊BLOG原创文章,转载或复制请以超链接形式并注明出处。