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

阅读数:913 2019 年 9 月 26 日 16:16

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

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

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

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 中,函数可以用任意数量的参数来调用,即使该数量不等于声明函数时参数的数量。缺少的参数将被视为未定义,多余的参数将被忽略,但是它们会被存储在类似数组的参数对象中。

复制代码
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);

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

发生了一件非常奇怪的事情。每个 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

评论

发布
用户头像
interesting
2019 年 09 月 27 日 11:34
回复
用户头像
基础知识掌握不牢,还想当然着用
2019 年 09 月 27 日 10:15
回复
没有更多了