JavaScript | 为何 ['1','7','11'].map(parseInt) = [1, NaN, 3] 【译】

Posted by lamyoung on September 10, 2019

javascript 是古怪的。不信🤨?试着用mapparseInt将一个字符串数组转成数字数组。将下面的段代码输入到控制台(在Chrome浏览器按 F12),并回车。

['1', '7', '11'].map(parseInt);

我们得到结果是[1, NaN, 3],而不是我们想要的[1, 7, 11]。为何?为了探究其原因,我们先探讨一些javascript观点。如果你是个’太长不看’,可以直接到总结部分。

true 或者 false

看下面这个 if-else 的简单条件语句。

if (true) {
    // 总是执行
} else {
    // 绝对不会执行
}

在这个例子中,if-else的语句总是true,所以if下的块语句总是执行,而else语句块绝不会执行。这很好理解,因为trueboolean。如果不是boolean作为条件的话会是怎么样呢?

if ("hello world") {
    // 执行这个?
    console.log("Condition is true");
} else {
    // 还是这个?
    console.log("Condition is false");
}

当你在控制台里执行后,会发现if下的语句块被执行,说明string对象"hello world"转成boolean值为true。所有的javascript的值要么是true要么是false。当在一个boolean环境下时,如if-else,都会被转成truefalse。那么这个转换规则是什么呢?可以参考以下这个规则:

⚠️ 除了false, 0, "" (空字符串), null, undefined, 和 NaN,其余的值都是转成true

也就是说,字符串"false","0",以及空对象{},空数组[]都是被转成true。你可以用Boolean方法验证下,例如Boolean("0")。在我们这个文章中,我们只用到0是转成false

基数

0 1 2 3 4 5 6 7 8 9 10

当我们从一数到九,我们有不同的字符表示每个数字(0-9)。然而,当数到十的时候,我们用两个字符表示(1和0)。这是因为我们采用的十进制,以10为基数(radix or base)。基数就是用一个字符表示的最小单位的数量。不同的计数方法有不同的基数,在不同的基数下一些相同的字符表示不同的数字。

十进制   二进制    十六进制
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,那么11表示的是数字3,如果基数是16,那么表示数字17。你可能注意到了,我们的例子中的11被parseInt后变成了3,也就是基数为2的时候。

Function arguments

Javascript中的function,不管声明时是几个变量,调用时可以传任意个参数。没有传的默认为undefined,超出的部分被忽略(但会保存在arguments对象中)。

function foo(x, y) {
    console.log(x, y);
}
foo(1, 2);      // 1  2
foo(1);         // 1  undefined
foo(1, 2, 3);   // 1  2

map()

map是数组Array的一个方法,返回的是一个数组,这个数组的值是由原数组的每一个值通过一个新方法映射的。例如,下面的代码就是把数组里所有的数乘以3。

function multiplyBy3(x) {
    return x * 3;
}
const result = [1, 2, 3, 4, 5].map(multiplyBy3);
console.log(result);   // [3, 6, 9, 12, 15];

现在我们可以用利用map()传入console.log打印所有元素。

[1, 2, 3, 4, 5].map(console.log);


哇🐸!发现打印的不仅仅是数值,还打印了index和整个数组。

[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()时,对于每一个迭代器,都传了3个参数给这个函数:currentValue(当前值), currentIndex(当前索引), 和 array(原数组)。这就是为什么会打印三个值了。好的👌,我们现在已经揭开部分谜底了。

综合起来看

parseInt需要两个参数:字符串和基数。当基数作为条件的时候是false时,基数默认为10。

parseInt('11');               // 11
parseInt('11', 2);            // 3
parseInt('11', 16);           // 17
parseInt('11', undefined);    // 11 (基数 是 false)
parseInt('11', 0);            // 11 (基数 是 false)

现在,让我们一步一步分解我们的例子:

['1', '7', '11'].map(parseInt);     // [1, NaN, 3]
// 第一次迭代: val = '1', index = 0, array = ['1', '7', '11']
parseInt('1', 0, ['1', '7', '11']); // 1

因为Boolean(0)false,所以默认的基数为10。parseInt只接收两个参数,所以第三个参数['1', '7', '11']被忽略了。字符串'1'在基数10中为数字1。

// 第二次迭代: 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在每次迭代传了3个参数给parseInt。第二个参数index(当前索引)被parseInt作为基数参数了。所以,每个字符串被解析成不同的基数。'7'用基数1,返回NaN,'11'用基数2,返回3。因为Boolean(0)false'1'用默认基数10,返回1

因此,我们想要的结果应该是如下代码:

['1', '7', '11'].map(numStr => parseInt(numStr));


原文链接🔗:https://medium.com/dailyjs/parseint-mystery-7c4368ef7b21