[译]为何 ['1', '7', '11'].map(parseInt) 在 Javascript 中返回了 [1, NaN, 3]

本篇文章采用意译,原文地址数组

你有没有以为 Javascript 是有点奇怪的。使用 mapparseInt 试着把字符串数组转化成整型数组,看看会发生什么。打开你的控制台,粘贴下面的代码而后执行。bash

['1', '7', '11'].map(parseInt);
复制代码

没有获得 [1,7,11,却获得了 [1, NaN, 3]。这到底是怎么回事?咱们首先须要讨论一些 Javascript 的概念,若是你以为太长,能够跳到最后看总结。函数

真值和假值

一个简单的 if-else 语句:ui

if (true) {
    // this always runs
} else {
    // this never runs
}
复制代码

在这个例子中,条件为真,执行 if 的语句块,条件为假执行 else 的语句块。这是显而易见的,由于 true 是个布尔值。咱们把非布尔值的东西做为条件,会发生什么?this

if ("hello world") {
    // 它会运行吗?
    console.log("Condition is truthy");
} else {
    // 它呢?
    console.log("Condition is falsy");
}
复制代码

在控制台里运行上面的代码,你会发现 if 语句块执行了。由于这个字符串对象是真值。spa

Javascript 对象要么是真值要么是假值。当在布尔值上下文中,好比 if-else 语句,对象被当作真或者假取决于它们的真实性。这个规则很简单:日志

除了:false, 0, ""(空字符串),null, undefined, 和 NaN 以外,其余的都是真值。code

使人困惑的是,字符串 "false" 和 字符串 "0" 还有空对象 {}, 空数组 [],都是真值。你能够经过给布尔函数传值来进行双向验证,好比 Boolean("0")cdn

咱们的目的是,知道 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, 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 接受两个参数:stringradix。若是 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));
复制代码

pic
相关文章
相关标签/搜索