为什么 [‘1’, ‘7’, ’11’].map(parseInt) 的结果是 [1, NaN, 3]?
Javascript 总是以超自然的方式执行我们的代码,这是一件很神奇的事情,如果不信的话,思考一下 ['1', '7', '11'].map(parseInt)
的结果是什么?你以为会是 [1, 7, 11]
吗?我都这么问了,那肯定不是:
['1', '7', '11'].map(parseInt) // 输出:(3) [1, NaN, 3]
要理解为什么为会这里,首先需要了解一些 Javascript 概念,如果你是一个不太喜欢阅读长文的人,那直接跳到最后看结论吧
真值与假值
请看下面示例:
if (true) { // 这里将启动都会被执行 } else { // 这里永远都不会被执行 }
在上面的示例中, if-else
声明为 true
,所以 if-block
会一直都执行,而 else-block
永远都会被忽略掉,这是个很容易理解的示例,下面我们来看看另一个例子:
if ("hello world") { // 这里会被执行吗? console.log("条件判断为真"); } else { // 或者会执行这里吗? console.log("条件判断为假"); }
打开你的开发者工具 console
面板,执行上面的代码,你会得到看到会打印出 条件判断为真 ,也就是说 "hello world"
这一个字符串被认为是 真值 。
在 JavaScript 中,truthy(真值)指的是在布尔值上下文中,转换后的值为真的值。所有值都是真值,除非它们被定义为 假值(即除 false、0、””、null、undefined 和 NaN 以外皆为真值)。
引用自: Mozilla Developer: Truthy(真值)
这里一定要划重点: 在布尔上下文中,除 false
、 0
、 ""
(空字符串)、 null
、 undefined
以及 NaN
外,其它所有值都为真值
基数
0 1 2 3 4 5 6 7 8 9 10
当我们从 0
数到 9
,每一个数字的表示符号都是不一样的(0-9),但是当我们数到 10
的时候,我们就需要两个不同的符号 1
与 0
来表示这个值,这是因为,我们的数学计数系统是一个十进制的。
基数,是一个进制系统下,能使用仅仅超过一个符号表示的数字的最小值, 不同的计数进制有不同的基数,所以,同一个数字在不同的计数体系下,表示的真实数据可能并不一样 ,我们来看一下下面这张在十进制、二进制以及十六进制不同值在具体表示方法:
十进制(基数:10) | 二进制(基数:2) | 十六进制(基数: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 |
18 | 10002 | 12 |
你应该已经注意到了, 11 在上表中,总共出现了三次。
函数的参数
在 Javascript 中,一个函数可以传递任何多个数量的参数,即使调用时传递的数量与定义时的数量不一致。缺失的参数会以 undefined
作为实际值传递给函数体,然后多余的参数会直接被忽略掉(但是你还是可以在函数体内通过一个类数组对象 arguments
访问到)。
function sum(a, b) { console.log(a); console.log(b); } sum(1, 2); // 输出:1, 2 sum(1); // 输出:1, undefined sum(1, 2, 3); // 输出:1, 2
map()
map()
是一个存在于数组原型链上的方法,它将其数组实例中的每一个元素,传递给它的第一个参数(在本文最开始的例子中就是 parseInt
),然后将每一个返回值都保存到同一个新的数组中,遍历完所有元素之后,将新的包含了所有结果的数组返回。
function multiplyBy3 (number) { return number * 3; } const result = [1, 2, 3, 4, 5].map(multiplyBy3); console.log(result); // 输出:[3, 6, 9, 12, 15]
现在,假想一下,我们想使用 map()
将一个数组中的每一个元素都打印到控制台,我可以直接将 console.log
函数传递给 map()
方法:
[1, 2, 3, 4, 5].map(console.log);
输出:
1 0 (5) [1, 2, 3, 4, 5] 2 1 (5) [1, 2, 3, 4, 5] 3 2 (5) [1, 2, 3, 4, 5] 4 3 (5) [1, 2, 3, 4, 5] 5 4 (5) [1, 2, 3, 4, 5]
是不是感觉有点神奇了?为什么会这样?来分析一下上面输出的内容都有些什么?
0 (5) [1, 2, 3, 4, 5]
再来看看下面这段代码:
[1, 2, 3, 4, 5].map((value, index, array) => console.log(value, index, array));
在控制台上试试执行上面这行代码,你会发现,它与 [1, 2, 3, 4, 5].map(console.log);
输出的结果是完全一致的,**总是会被我们忽略的一点是, map()
方法会将三个参数传递传递给它的函数,分别是 currentValue
(当前值)、 currentIndex
(当前索引)以及 array
本身,这就是,上面为什么结果有三列的原因,如果我们只是想将每一个值打印,那么需要像下面这样写:
[1, 2, 3, 4, 5].map((value) => console.log(value));
回到最开始的问题
现在让我们来回顾一下本文最开始的问题:
为什么 ['1', '7', '11'].map(parseInt)
的结果是 [1, NaN, 3]
?
来看看 parseInt()
是一个什么样的函数:
parseInt(string, radix)
将一个字符串 string
转换为 radix
进制的整数, radix
为介于 2-36
之间的数。
详情: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/parseInt
这里还有一条需要注意的是,虽然我们一般都是使用 parseInt('11')
这样的调用方式,认为是将 11
按十进制转换成数字,但是,请在使用的时候,永远都添加 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', '7', '11'].map((value, index, array) => parseInt(value, index, array))
根据前面的知识,我们应该知道, parseInt(value, index, array)
中的 array
会被忽略(并放入 arguments
对象中。
那么,完整的代码就是下面这样的:
['1', '7', '11'].map((value, index, array) => parseInt(value, index))
- 遍历到第一个元素时:
parseInt('1', 0);
radix` 为 `0`,是一个假值,在我们的控制台中,一般将使用 `10` 进制,所有,得到结果 `1`
- 遍历到第二个元素时:
parseInt('7', 1)
在一个 `1` 进制系统中,`7` 是不存在的,所以得到结果 `NaN`(不是一个数字)
- 遍历到第三个元素时:
parseInt('11', 2)
在一个 `2` 进制系统中,`11` 就是进十制的 `3`
总结
['1', '7', '11'].map(parseInt)
的结果是 [1, NaN, 3]
的原因是因为, map()
方法是向传递给他的函数中传递三个参数,分别为当前值,当前索引以及整个数组,而 parseInt
函数接收两个参数:需要转换的字符串,以及进制基数,所以,整个语句可以写作: ['1', '7', '11'].map((value, index, array) => parseInt(value, index, array))
, array
被 parseInt
舍弃之后,得到的结果分别是: parseInt('1', 0)
、 parseInt('7', 1)
以及 parseInt('11', 2)
,也就是上面看到的 [1, NaN, 3]
。
正确的写法应该是:
['1', '7', '11'.map(numStr => parseInt(numStr, 10));
你也许感兴趣的:
- ECMAScript 2024新特性
- 【外评】JavaScript 变得很好
- 一长串(高级)JavaScript 问题及其解释
- 不存在的浏览器安全漏洞:PDF 中的 JavaScript
- Python 里的所有双下划线(dunder)方法、函数和属性
- 【程序员搞笑图片】JavaScript
- JavaScript 膨胀于 2024 年
- 解码为什么 JS 中的 0.6 + 0.3 = 0.89999999999999 以及如何解决?
- 用 JavaScript 实现的 17 个改变世界的方程式
- 【译文】Dropbox:我们如何将 JavaScript 打包程序的大小减少 33% 的
你对本文的反应是: