摘要:本文详细解析了一道JavaScript面试题,涉及map函数和parseInt函数的用法。当parseInt作为map的迭代函数时,由于缺少第二个参数(进制),导致结果并非预期的[1,2,3],而是[1,NaN,NaN]。文章还通过多个例子解释了parseInt的进制解析规则,并提供了额外的变种题供读者思考。
面试题:请你说出以下代码的执行结果
['1', '2', '3'].map(parseInt);
如果大家第一次做这道题,几乎都会认为答案是 [1,2,3], 原因很简单:paseInt 会把一个字符串转化为整数。 ‘1’, ‘2’, ‘3’ 转化为整数分别是 1 , 2 , 3,所以,以上代码会循环执行 3 次,分别返回 1 , 2 , 3。
知识回顾
map() 函数
在大多数场景下,我们使用 map 函数只会使用到它的第一个参数,也就是当前遍历的元素。比如以下代码,第一次 item 是 '1' ,第二次 item 是 '2'…
我们忽略了 map 函数的第二个参数和第三个参数。map 函数的第二个参数是当前遍历元素的下标(或者说索引)。map 函数的第三个参数是数组本身。
const result = ["1", "2", "3"].map((item, index, arr) => {
console.log(arr); // ['1', '2', '3']
console.log(index); // 当前索引
console.log(item); // 当前遍历的元素
return item * 2;
});
parseInt() 函数
parseInt() 函数可解析一个字符串,并返回一个整数。
语法格式:parseInt(string, radix)
parseInt接收两个参数:
- 第一个参数
string
:要被解析的字符串,如果不是字符串会被转换,忽视空格符 - 第二个参数
radix
:要解析的数字的基数。该值介于2 ~ 36
之间。默认值为10,表示十进制。这个参数表示将前面的字符从radix进制转化为十进制,即,radix 用于描述你当前传入的字符串是 几进制。
1、在没有指定基数,或者基数为0的情况下,parseInt()会根据string
参数来判断数字的基数。
也就是说,如果你没有传第二个参数 radix或者 radix为0的情况下,那么根据以下规则来自动判断 string 是几进制:
① 如果字符串string以"0x"或者"0X"开头, 则基数是16 (16进制);
② 如果字符串string以"0"开头, 基数是8(八进制)或者10(十进制),那么具体是哪个基数由实现环境决定。
ECMAScript 5 规定使用10,但是并不是所有的浏览器都遵循这个规定。因此,永远都要明确给出radix参数的值。
③ 如果字符串string以其它任何值开头,则基数是10 (十进制);
④ 如果你传第二个参数 radix,radix的范围需要在2 ~ 36之内。如果在2 ~ 36之外会返回NaN。
2、如果radix
在2 ~ 36之外
会返回NaN。
我们看以下几个例子:
示例1
console.log(parseInt(5, 8));
结果:5
解释:将字符串"5"解析为十进制数,因为第二个参数为8,表示使用八进制解析。但是"5"并不是八进制数字,所以解析结果为十进制数5。
示例2
console.log(parseInt(5, 2));
结果为:NaN
解释:将字符串"5"解析为二进制数,因为第二个参数为2,表示使用二进制解析。但是"5"并不是有效的二进制数字,所以解析结果为NaN(Not a Number)。
示例3
console.log(parseInt(5, 0));
结果为:5
解释:第二个参数为0时,parseInt函数会根据字符串的前缀来判断要使用的进制。由于"5"没有前缀,所以默认按照十进制解析,结果为十进制数5。
示例4
console.log(parseInt(5, 1));
结果为:NaN
解释:第二个参数为1,表示使用基数为1进行解析。在数学中,基数必须大于1,因此,无法解析为有效的数字,结果为NaN。
示例5
console.log(parseInt(123, 5));
结果为:38
解释:将字符串"123"解析为五进制数,因为第二个参数为5,表示使用五进制解析。将字符串中的每个字符按照五进制转换为数字,得到1*5^2 + 2*5^1 + 3*5^0 = 38。因此,解析结果为38。
题目分析
理解了 map 和 parseInt 之后,我们再来看 ['1', '2', '3'].map(parseInt);
其实 ['1', '2', '3'].map(parseInt) 等同于 parseInt(item, index, array));
因为parseInt
只接收2个参数,因此,map的callback函数只把(item:元素值, index:索引)这两个有效参数传给了parseInt,第三个参数无效。
那么这个原题就可以分解为求解下面这三个函数的值,并返回结果
第一次循环:
parseInt('1', 0); // 参数:'1',进制值为默认的十进制(0)
// 返回结果:1
第二次循环:
parseInt('2', 1); // 参数:'2',进制值为 1
// 返回结果:NaN(因为2是无效的一进制数字)
第三次循环:
parseInt('3', 2); // 参数:'3',进制值为 2
// 返回结果:NaN(因为3是无效的二进制数字)
因此,返回的结果是:[1, NaN, NaN]
哎,所以为了避免这个坑,平时写 map
还是不要偷懒了,完整的写法才更直观且更容易维护。
['1', '2', '3'].map(value => parseInt(value));
面试题的变种
console.log(parseInt(1/0, 19)) // 18
解析:parseInt里面有两个参数,第二个参数是19,十九进制包括(0,1,2,3,4,5,6,7,8,9,a,b,c,d,e,f,g,h,i);然后分析第一个参数1/0,它的运算结果是 Infinity,那么相当于要把 Infinity 从19进制转化为十进制,我们从第一个字符I开始 – 19进制有i,表示18,接下来字母n ,-- 哦豁,19进制里面没有 n,既然不认识,那就不管了,立即返回i(对应十进制中的18),所以返回结果为:18。
console.log(parseInt(false, 16)) // 250
解析:parseInt里面有两个参数,第二个参数16,十六进制包括(0,1,2,3,4,5,6,7,8,9,a,b,c,d,e,f);然后分析第一个参数false,转化为字符串(parseInt第一个参数非字符串先转化为字符串)==> false。,从第一个字符开始分析,f ,a,接下来是 l,16进制里面没有 l。立即返回fa (十六进制的fa转换成十进制等于250),结果为:250
console.log(parseInt(parseInt, 16)) // 15
解析:parseInt里面有两个参数,第二个参数是16,十六进制包括(0,1,2,3,4,5,6,7,8,9,a,b,c,d,e,f);然后分析第一个参数 parseInt,转化为字符串,我们看看String(parseInt)结果是什么, function parseInt() { [native code] }, 好的,从第一个字符开始分析,f认识,u不认识,立即返回f (十六进制的f转换成十进制等于15), 返回结果:15
console.log(parseInt({}, 16)) // NaN
解析:parseInt里面有两个参数,第二个参数是16,十六进制包括(0,1,2,3,4,5,6,7,8,9,a,b,c,d,e,f);然后分析第一个参数{}, 我们看看String({})是个啥,[object Object], 第一个字符是[, 不认识,那就返回 NaN 吧。
最后来总结一下:
① map函数需要注意回调函数的三个参数涵义,编写代码时尽量写完整,防止掉坑。
② parseInt第一个参数如果不是字符串,先尝试转换为字符串,再进行进制转化。
③ parseInt第二个参数根据上面的转化规则进行分析即可
④ parseInt第二个参数范围到36,其实是0~9的十进制,加上26个字母,即三十六进制
相信只要掌握了parseInt的转化规则,按照套路来拆解分析,下次再遇到类似的奇葩问题,也能手到擒来。
参考资料:parseInt()函数绝不是你想的那么简单 | 前端面试官:parseInt( )执行结果是多少?_哔哩哔哩