您的位置:首页 > Web前端 > JavaScript

奇怪的JavaScript:map和parseInt的反常应用

2019-09-24 18:02 1751 查看


作者 | Eric Tong
译者 | 刘雅梦
编辑 | 张之栋
在 JavaScript 中,当使用 map 和 parseInt 将字符数组转换为整数时,却出现了奇怪的现象。控制台输入 ['1', '7', '11'].map(parseInt)后,得到的结果是 [1, NaN, 3] ,而不是 [1, 7, 11] 。本文将通过解释一些相关的 JavaScript 概念,解答出现这种状况的原因。

JavaScript 很奇怪。不相信吗?尝试使用 map 和 parseInt 将字符数组转换为整数。打开控制台(Chrome 上的快捷键是 F12),粘贴下面的代码,然后按回车键(或将焦点放到下面)。我们得到的不是一个整数数组 [1, 7, 11] ,而是 [1, NaN, 3] 。为什么呢?
['1', '7', '11'].map(parseInt);

要想了解到底发生了什么,我们首先要讨论一些 JavaScript 的概念。如果你想要一个 TLDR(Too Long; Didn’t Read:太长了,不想读),我在本文的末尾提供了一个快速总结。

真 & 假
下面是 JavaScript 中的一个简单 if-else 语句:
if (true) {
    // 此处总会运行
} else {
    // 此处不会运行到
}
在本例中,if-else 语句的条件为 true,因此它总会执行 if 块,而忽略 else 块。这是一个很简单的例子,因为 true 是一个布尔值。如果我们把一个非布尔值作为条件呢?
if ("hello world") {
    // 这将会运行吗?
    console.log("Condition is truthy");
} else {
    // 还是运行这里?
    console.log("Condition is falsy");
}

尝试在开发人员的控制台(在 Chrome 上按 F12)中运行此代码。我们会发现 if 块运行了。这是因为字符串对象“hello world”是真。

每个 JavaScript 对象要么是真,要么是假。当将它们放到布尔上下文中时,如放在 if-else 语句中,对象将根据其真实性被视为 true 或 false。那么哪些对象是真,哪些是假呢?这里有一个简单的规则:

除了 false、0、“”(空字符串)、null、undefined、及 NaN 以外, 所有值都是真。

令人困惑的是,这意味着字符串 “false” 、字符串 “0” 、空对象 {} 和空数组 [] 都是真。我们可以通过将一个对象传递到布尔函数中(例如 Boolean("0"); 中),来对其进行双重检查。

就我们的目的而言,只要记住 0 是假就足够了。

    基数    

0 1 2 3 4 5 6 7 8 9 10

当我们从 0 数到 9 时,每个数字(0-9)都有不同的符号。然而,一旦达到 10,我们需要两个不同的符号(1 和 0)来表示这个数字。这是因为我们的十进制计数系统的基数(或基础)是 10。

基数是一个最小的数字,它只能由多个符号来表示。不同的计数系统有不同的基数,因此,相同的数字在不同的计数系统中,可以表示不同的数字。

十进制   二进制    十六进制
基数 =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

例如,观察上表,我们可以看到在不同的计数系统中,相同的数字 11 可以表示不同的数字。如果基数是 2,那么它指的是数字 3。如果基数是 16,那么它指的是数字 17。

你可能已经注意到了,在我们的示例中,当输入为 11 时,parseInt 返回 3,这对应于上表中的二进制列。

函数的参数
在 JavaScript 中,函数可以用任意数量的参数来调用,即使该数量不等于声明函数时参数的数量。缺少的参数将被视为未定义,多余的参数将被忽略(但是它们会被存储在类似数组的参数对象中: https://javascriptweblog.wordpress.com/2011/01/18/javascripts-arguments-object-and-beyond/)。
function foo(x, y) {
    console.log(x);
    console.log(y);
}
foo(1, 2); // log 输出 1, 2
foo(1); // log 输出 1, undefined
foo(1, 2, 3); // log 输出 1, 2
map()

到了我们要讲的部分了!

Map 是数组原型中的一个方法,它返回一个新的数组,其中包含将原始数组的每个元素传递到函数中的结果。例如,下面的代码将数组中的每个元素乘以 3:
function multiplyBy3(x) {
    return x * 3;
}
const result = [1, 2, 3, 4, 5].map(multiplyBy3);
console.log(result); // log 输出 [3, 6, 9, 12, 15];
现在,假设我想使用 map() 记录每个元素(没有返回语句)。我应该可以将 console.log 作为参数传递到 map() 中…... 对吧?
[1, 2, 3, 4, 5].map(console.log);

发生了一件非常奇怪的事情。每个 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 有两个参数:string 和 radix 。如果提供的基数为假,那么默认情况下,基数将被设置为 10。
parseInt('11');                => 11
parseInt('11', 2);             => 3
parseInt('11', 16);            => 17
parseInt('11', undefined);     => 11 (基数为假)
parseInt('11', 0);             => 11 (基数为假)
现在让我们一步一步地运行我们的示例。
['1', '7', '11'].map(parseInt); => [1, NaN, 3]
第一次第一次迭代 index = 0, array = ['1', '7', '11']
parseInt('1', 0, ['1', '7', '11']); => 1
因为 0 是假,所以基数设置为默认值 10。parseInt() 只接受两个参数,因此会忽略第三个参数 ['1', '7', '11'] 。以 10 为基数的字符串 '1' 表示数字 1。
// 第二次迭代 index = 1, array = ['1', '7', '11']
parseInt('7', 1, ['1', '7', '11']); => NaN
在基数为 1 的系统中,符号 '7' 不存在。与第一次迭代一样,最后一个参数将被忽略。因此,parseInt() 返回 NaN。
// 第三次迭代, index = 2, array = ['1', '7', '11']
parseInt('11', 2, ['1', '7', '11']); => 3

在基数为 2(二进制)的系统中,符号 '11' 表示数字 3。最后一个参数被忽略。

总结 (TLDR)

['1', '7', '11'].map(parseInt) 不能按预期工作,是因为 map 在每次迭代时都会将三个参数传递到 parseInt() 中。第二个参数 index 作为 radix 参数传递给 parseInt。因此,数组中的每个字符串都使用不同的基数进行解析。'7' 按照基数 1 进行解析,即 NaN;'11' 按照基数 2 进行解析,即 3。'1' 按照默认基数 10 进行解析,因为它的索引 0 是假。

因此,下面的代码能按预期工作:
['1', '7', '11'].map(numStr => parseInt(numStr));

英文原文: https://medium.com/dailyjs/parseint-mystery-7c4368ef7b21

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: