首先,我们从一个经典的面试题作为开胃菜,请慢慢品尝。
一个面试题引发的思考
下面这个例子是一道经典的面试题:
['1', '2', '3'].map(parseInt);
相信不少同学第一次碰到这个题目时,非常自然的写出了答案:[1,2,3]
。
也有一部分同学揣测了一下出题者的意图,既然是面试题,绝不是表面上想的那么简单。
确实如此,这道题不简单,最终结果是 [1, NaN, NaN]
。
我们分析拆解一下,这道题涉及到了两个知识点:
map
函数parseInt
函数
接下来具体分析下这两个函数
map函数
map
函数,相信大家已经再熟悉不过了
map
函数可以将一个数组映射为一个新数组。它接收一个callback
回调函数作为参数,这个回调函数体现了将原数组映射成新数组的映射关系。
原数组在循环遍历数组每一项时,都会调用一次callback
回调函数,并传入三个参数:
- 当前正在遍历的元素
- 元素索引
- 原数组本身 (这个参数基本不使用)
callback
函数对当前遍历的元素进行包装执行,得到的返回值就是新数组中对应的结果
const result = ['1', '2', '3'].map((item, index, arr) => item * 2);
// result --> [2, 4, 6]
所以原题其实等同于
['1', '2', '3'].map((item, index, array) => parseInt(item, index, array));
那么接下来就该parseInt出场了
parseInt函数
parseInt() 函数可解析一个字符串,并返回一个整数。
语法格式:parseInt(string, radix)
parseInt接收两个参数:
- 第一个参数
string
:要被解析的字符串,如果不是字符串会被转换,忽视空格符 - 第二个参数
radix
:要解析的数字的基数。该值介于2 ~ 36
之间。默认值为10,表示十进制。这个参数表示将前面的字符从radix进制转化为十进制-
- 在没有指定基数,或者基数为0的情况下,parseInt()会根据
string
参数来判断数字的基数。
- 如果字符串
string
以"0x"或者"0X"开头, 则基数是16 (16进制). - 如果字符串
string
以"0"开头, 基数是8(八进制)或者10(十进制),那么具体是哪个基数由实现环境决定。ECMAScript 5 规定使用10,但是并不是所有的浏览器都遵循这个规定。因此,永远都要明确给出radix参数的值。 - 如果字符串
string
以其它任何值开头,则基数是10 (十进制)。
- 在没有指定基数,或者基数为0的情况下,parseInt()会根据
-
- 如果
radix
在2 ~ 36之外
会返回NaN。
- 如果
-
我们来看几个栗子🌰:
// 例1
console.log(parseInt(3, 8)); // 3
// 例2
console.log(parseInt(3, 2)); // NaN
// 例3
console.log(parseInt(3, 0)); // 3
// 例4
console.log(parseInt(3, 1)); // NaN
// 例5
console.log(parseInt(123, 5)); // 结果为38
解析如下:
- 例1:
parseInt
里面有两个参数,第二个参数是8,表示要将八进制
的3转换为十进制的结果,八进制中有3,转化为十进制还是3,所以返回结果为3 - 例2:
parseInt
里面有两个参数,第二个参数是2,表示要将二进制
的3转化为十进制,额...,不好意思,二进制中并没有3,所以返回NaN
- 例3:
parseInt
里面有两个参数,第二个参数是0,根据规则1,默认就是十进制,直接返回3 - 例4:
parseInt
里面有两个参数,第二个参数是1,根据规则2,1在2 ~ 36之外
,直接返回NaN
。 - 例5:
parseInt
里面有两个参数,第二个参数是5,表示要将五进制
的123转化为十进制,结果为38 => (1*5^2 + 2*5^1 + 3*5^0 = 38
)
看到这里,我们就可以清楚的理解parseInt的用法了。
接下来我们来分析下原题:
因为parseInt
只接收2个参数,因此map的callback函数只把(item:元素值, index:索引)这两个有效参数传给了parseInt,第三个参数无效。
那么这个原题就可以分解为求解下面这三个函数的值,并返回结果
// 实际代码运算等同于
parseInt('1', 0) // 1 (基数为0,此时表示十进制的1,结果还是1)
parseInt('2', 1) // NaN (基数为1,不在`2 ~ 36`范围内,返回NaN)
parseInt('3', 2) // NaN (前面已经分析过了,二进制中没有3,返回NaN)
所以为了避免这个坑,平时写 map
还是不要偷懒了,完整的写法才更直观且更容易维护。
['1', '2', '3'].map(value => parseInt(value));
面试题的变种
除了上面提到的经典面试题,parseInt
还衍生除了其他的变种,来看看下面这些栗子🌰
console.log(parseInt(1/0, 19)) // 18
console.log(parseInt(false, 16)) // 250
console.log(parseInt(parseInt, 16)) // 15
console.log(parseInt({}, 16)) // NaN
看到这些奇葩代码,先别懵逼,咱稍微分析分析:
-
console.log(parseInt(1/0, 19))
解析:
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进制没这玩意儿,既然不认识,那就不管了,立即返回i
(对应十进制中的18),所以返回结果为18 -
console.log(parseInt(false, 16))
解析:
parseInt
里面有两个参数,第二个参数16,十六进制
包括(0,1,2,3,4,5,6,7,8,9,a,b,c,d,e,f);然后分析第一个参数false,转化为字符串(parseInt第一个参数非字符串先转化为字符串)==>false
, 好的,从第一个字符开始分析,f
认识,a
认识,l
哦豁,不认识,立即返回fa
(十六进制的fa转换成十进制等于250),结果为250 -
console.log(parseInt(parseInt, 16))
解析:
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))
解析: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
的转化规则,按照套路来拆解分析,下次再遇到类似的奇葩问题,也能手到擒来。