[译]为什么 ['1', '7', '11'].map(parseInt) 在 Javascript 中返回了 [1, NaN, 3]
本篇文章采用意译,原文地址
你有没有觉得 Javascript 是有点奇怪的。使用 map
和 parseInt
试着把字符串数组转化成整型数组,看看会发生什么。打开你的控制台,粘贴下面的代码然后执行。
['1', '7', '11'].map(parseInt);
没有得到 [1,7,11
,却得到了 [1, NaN, 3]
。这究竟是怎么回事?我们首先需要讨论一些 Javascript 的概念,如果你觉得太长,可以跳到最后看总结。
真值和假值
一个简单的 if-else 语句:
if (true) {
// this always runs
} else {
// this never runs
}
在这个例子中,条件为真,执行 if 的语句块,条件为假执行 else 的语句块。这是显而易见的,因为 true 是个布尔值。我们把非布尔值的东西作为条件,会发生什么?
if ("hello world") {
// 它会运行吗?
console.log("Condition is truthy");
} else {
// 它呢?
console.log("Condition is falsy");
}
在控制台里运行上面的代码,你会发现 if 语句块执行了。因为这个字符串对象是真值。
Javascript 对象要么是真值要么是假值。当在布尔值上下文中,比如 if-else 语句,对象被当做真或者假取决于它们的真实性。这个规则很简单:
除了:false, 0, ""(空字符串),null, undefined, 和 NaN
之外,其他的都是真值。
令人困惑的是,字符串 "false" 和 字符串 "0" 还有空对象 {}, 空数组 [],都是真值。你可以通过给布尔函数传值来进行双向验证,比如 Boolean("0")
。
我们的目的是,知道 0 是假值就足够了。
基数
0 1 2 3 4 5 6 7 8 9 10
当我们从 0 数到 9 的时候,每个不同的符号代表一个数字。然而到 10 的时候,我们需要两个符号来表示。这是因为我们是十进制系统,它的基数是 10.
基数是用一个以上的符号表示的最小数字。不同的计数系统有着不同的基数。
DECIMAL BINARY HEXADECIMAL
RADIX=10 RADIX=2 RADIX=16
0 0 0
1 1 1
2 10 2
3 11 3
4 100 4
5 101 5
6 110 6
7 111 7
8 1000 8
9 1001 9
10 1010 A
11 1011 B
12 1100 C
13 1101 D
14 1110 E
15 1111 F
16 10000 10
17 10001 11
看看上面的表,数字 11 在不同的系统中表示也不同。如果基数是 2,它是 3,如果基数是 16,它是 17.
我们上面的例子中吧 '11' 转换成了 3,就是上面表格二进制的表现。
函数参数
Javascript 中的函数可以传递任意数量的参数,即使跟函数声明的参数不相等。缺少的参数会被当做 undefined,多余的参数会被忽略(但是会存储在类数组的 arguments 对象中)。
function foo(x, y) {
console.log(x);
console.log(y);
}
foo(1, 2); // logs 1, 2
foo(1); // logs 1, undefined
foo(1, 2, 3); // logs 1, 2
map()
马上就到我们的重点了!
Map 是 Array 原型上的方法,它返回一个每个原始数组的元素传入函数的结果的新数组。比如下面代码,每个元素在数组中乘 3:
function multiplyBy3(x) {
return x * 3;
}
const result = [1, 2, 3, 4, 5].map(multiplyBy3);
console.log(result); // logs [3, 6, 9, 12, 15];
那么现在如果我需要日志输出每个元素,是不是使用 map()
然后传入 console.log
就可以了呢?
[1, 2, 3, 4, 5].map(console.log);
![[译]为什么 ['1', '7', '11'].map(parseInt) 在 Javascript 中返回了 [1, NaN, 3]](https://static.blogweb.cn/article/14d4ed851d24453c93ea75dff3d06728.webp)
奇怪的事,不只是输出了值,同时把索引和全部数组也输出了。
[1, 2, 3, 4, 5].map(console.log);
// 上面的代码等于
[1, 2, 3, 4, 5].map(
(val, index, array) => console.log(val, index, array)
);
// 不等于
[1, 2, 3, 4, 5].map(
val => console.log(val)
);
当给 map()
传入一个方法时,对于每次迭代,都会有三个参数传入方法:currentValue, currentIndex,
和全部 array
。这就是为什么每次迭代都会输出全部三个实体内容。
现在离解开我们的谜题越来越近了。
揭晓答案
ParseInt
接受两个参数:string
和 radix
。如果 radix 没有提供,默认值就是 10.
parseInt('11'); => 11
parseInt('11', 2); => 3
parseInt('11', 16); => 17
parseInt('11', undefined); => 11 (没有 radix)
parseInt('11', 0); => 11 (没有 radix)
现在一步步走进之前的例子:
['1', '7', '11'].map(parseInt); => [1, NaN, 3]
// 第一次遍历: val = '1', index = 0, array = ['1', '7', '11']
parseInt('1', 0, ['1', '7', '11']); => 1
因为 0 是假值,所以 radix 基数取值为 10。 因为 parseInt 只接受两个参数,所以第三个 ['1', '7', '11']
参数被忽略了。
// 第二次遍历: val = '7', index = 1, array = ['1', '7', '11']
parseInt('7', 1, ['1', '7', '11']); => NaN
因为在基数为 1 的系统中, 7
不存在。同时第三个参数仍然跟第一次迭代一样被省略。所以 parseInt()
返回了 NaN
。
// 第三次迭代: val = '11', index = 2, array = ['1', '7', '11']
parseInt('11', 2, ['1', '7', '11']); => 3
基数为 2 的系统中, 符号 11
得出数字 3。最后的参数被省略。
总结
['1', '7', '11'].map(parseInt)
没有像预期那样工作,是因为 map 给 parseInt 传递了三个参数。第二个参数 index 作为 radix 参数传入了 parseInt。所以每个数组的字符串使用了不同的基数来解析。'7' 被解析为基数为 1 的结果,也就是 NaN,11
被解析为基数为 2 的结果,也就是 3。1
被作为默认值解析,因为索引 0 是假值。所以,下面的代码才能正常工作:
['1', '7', '11'].map(numStr => parseInt(numStr));
![[译]为什么 ['1', '7', '11'].map(parseInt) 在 Javascript 中返回了 [1, NaN, 3]](https://static.blogweb.cn/article/a7d7f8f268484852871aa57fcf87c14f.webp)
转载自:https://juejin.cn/post/6844903874755559437