阅读 1374

【译】为什么在JavaScript中['1', '7', '11'].map(parseInt)返回[1, NaN, 3]

本文摘自:《Why ['1', '7', '11'].map(parseInt) returns [1, NaN, 3] in Javascript》

译者:战五渣

???

JavaScript真的很奇怪,不相信我?那我们尝试用mapparseInt将字符串数组转换成整数数组。在chrome上启动控制台(win为F12键,mac为cammand+option+i),复制粘贴下面的代码,然后回车运行。

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

运行以后我们得到的竟然不是一个整数数组[1, 7, 11],而是[1, NaN, 3]。What??为了知道这到底发生了什么,我们首先需要聊聊JavaScript的一些概念。如果你喜欢TLDR(五渣注:TLDR就是too long,don't read“太长不想看了”),我在文章的结尾写了一个总结。

真值 和 假值

这有一个JavaScript的简单的if-else语句:

if (true) {
    // 这总是执行
} else {
    // 这永远不会执行
}
复制代码

在这种判断条件一直是true的情况下,会一直执行if中的代码(if-block),而else中的代码(else-block)永远不会执行。这很简单是不是,就是因为true就是一个布尔值,那如果我们将一个非布尔值的值作为条件会是什么样的呢??

if ('hello world') {
    // 这还会运行吗?
    console.log('判断条件是真')
} else {
    // 还是运行这里?
    console.log('判断条件是假')
}
复制代码

让我们在控制台中运行这段代码。

我们能发现这种情况下,实际运行的是if-block,这是因为字符串hello world为真值(五渣注:因为js会进行‘隐式转换’)。

每个JavaScript对象不是真值就是假值。当放置在布尔型上下文(例如if-else语句)中时,根据对象的真实性(也就是进行隐式转换)将其视为true或者false。那什么值会被判断为真值,什么值会被判断为假值呢?遵循下面这个简单的规则:

除了后面这些值外,其他的值都会被判断为真值:false0''(空字符串)、nullundefinedNaN

这就很让人困惑了,这就意味着'false'字符串false,'0'字符串0,一个空对象{}和一个空数组[]都是真值,咱可以通过把这些不确定的对象传给Boolean函数(例如Boolean('0'))来看看是不是真如上面所说的。

就我们现在而言,只要知道0会判断为false就行了

基数

0 1 2 3 4 5 6 7 8 9 10
复制代码

当我们从0数到10的时候,每个数(0-9)都是都是一个单独不同的数字,但是,一旦数到10,就需要两个数字(1和0)来表示这个数。这是因为我们十进制的基数为十,逢十进一。

基数用一个以上的数字表示的最小的数。不同的进制具有不同的基数,因此,相同的数字在不同的进制中可以表示不同的大小。

十进制    二进制    十六进制
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在不同的进制中表示不同的数字。在二进制中的11,对应的十进制为3。在十六进制中的11,则对应的十进制为17。

小伙伴们可能已经注意到了,在我们标题中,当输入11时parseInt返回3,它对应于表上的二进制,还是不懂的话,我们继续往下看。

函数参数

在JavaScript中,可以在调用函数的时候传入任意数量的参数,即使传入的的参数数量跟函数声明时的参数数量不同,也会按照缺少的参数将被视为未定义,多余的参数将被忽略来执行(但是所有的参数都存储在类数组arguments对象中)

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

Array.prototype.map()方法

我们马上就要讲到重点了!

map()方法是Array.prototype上的一个方法,该方法返回一个新数组,其结果是该数组中的每个元素都调用一个提供的函数后返回的结果。例如,以下代码将数组中的每个元素都乘以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)
复制代码

见证奇迹的时刻,每个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(),对于每次迭代,都有currentValuecurrentIndex和完整的array三个参数传给这个函数。这就是为什么console.log每条输出都是三个值的原因。

现在我们了解了解开title这个谜题的所有条件!

Mix这些条件

parseInt接受两个参数stringradix,如果传入的radix参数是不正确的,则默认情况下,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]

// 第一次迭代,传入参数为 val = '1', index = 0, array = ['1', '7', '11']

parseInt('1', 0, ['1', '7', '11']);     // 1
复制代码

第二个参数是基数,传入的0是假值,所以被设置成默认的十进制。因为parseInt只接受两个参数,所以第三个整个数组的这个参数被忽略掉了。所以'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,最后一个参数被忽略

总结(TLDR:too long, don't read)

['1', '7', '11'].map(parseInt)没有像我们想象中的那样输出,因为在每次迭代中都会向map中的parseInt传递三个参数,第二个参数index作为radix参数传递到了parseInt中。因此,使用了不同的进制解析数组中的每个字符串'11'被解析为基数为2即二进制,所以返回3。'7'被解析为基数为1,所以返回NaN'1'的索引是0,0是个假值,所以被重置为十进制,所以还是返回1。

所以我们如果想按照我们的想法运行的话,我们得这么写

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

// 传进去一个参数,就ok了
复制代码

我是前端战五渣,一个前端界的小学生。

关注下面的标签,发现更多相似文章
评论