首先看下 parseInt
的函数签名
/**
* Converts a string to an integer.
* @param s 要被解析的值。如果参数不是一个字符串,则将其转换为字符串(使用 ToString 抽象操作)。字符串开头的空白符将会被忽略。
* @param radix 一个介于2和36之间的整数(数学系统的基础),表示上述字符串的基数。
* 如果不提供这个参数,以 `0x` 开头的字符串会被识别成16进制数.
* 其它情况,不同环境有不同的处理方式,一般会被识别成10进制数
* 如果传入 0 null undefined 等,当不提供这个参数处理
* 如果传入不是数字的会把数字,会把参数转成数字,如果没法转化,当不提供这个参数处理
* 不提供这个参数不同平台可能会有不同,所以,请显式提供这个参数
*/
declare function parseInt(s: string, radix?: number): number;
可以看到 parseInt
其实有两个参数,一个是必填的 s
,类型是 string
;
还有一个可选的 radix
,用来表示进制,例如填写 10
的话,会把第一个参数解析成十进制,如果填写 16
,会把传进来的字符串解析成十六机制,原则上,这个参数最好都填写。
返回值是一个整数。如果被解析参数的第一个字符无法被转化成数值类型,则返回 NaN
。
几个简单的例子
以下这堆都返回 15
parseInt('0xF', 16);
parseInt('F', 16);
parseInt('17', 8);
parseInt(021, 8);
parseInt('015', 10); // parseInt(015, 10); 返回 15
parseInt(15.99, 10);
parseInt('15,123', 10);
parseInt('FXX123', 16);
parseInt('1111', 2);
parseInt('15 * 3', 10);
parseInt('15e2', 10);
parseInt('15px', 10);
parseInt('12', 13);
以下例子均返回 -15
parseInt('-F', 16);
parseInt('-0F', 16);
parseInt('-0XF', 16);
parseInt(-15.1, 10);
parseInt(' -17', 8);
parseInt(' -15', 10);
parseInt('-1111', 2);
parseInt('-15e1', 10);
parseInt('-12', 13);
以下例子都返回 NaN
parseInt('Hello', 8); // 根本就不是数值
parseInt('546', 2); // 除了“0、1”外,其它数字都不是有效二进制数字
parseInt('zz', 35); // z 是 35,在 35 进制里面不可能有 35
返回 NaN
的例子改一下
parseInt('Hello', 18); // 320, H = 17 e = 14 l = 21,可以解析的 He 17 * 18 ^ 1 + 14 * 18 ^ 0 = 320
// 188275, H = 17 e = 14 l = 21,可以解析的 Hell 17 * 22 ^ 3 + 14 * 22 ^ 2 + 21 * 22 ^ 1 + 21 * 22 ^ 0 = 188275
parseInt('Hello', 22);
parseInt('546', 10); // 546 这个不说了
parseInt('zz', 36); // 1295 z = 35,35 * 36 ^ 1 + 35 * 36 ^ 0 = 1295
传入数字,以下例子都返回 4
,先 toString()
,然后小数点是没法解析的
parseInt(4.7, 10);
parseInt(4.7 * 1e22, 10); // 非常大的数值变成 4
parseInt(0.00000000000434, 10); // 非常小的数值变成 4
最近的一些面试题和变种
[1,2,3].map(parseInt)
// [1, NaN, NaN]
map(callbackfn: (value: T, index: number, array: ReadonlyArray) => U, thisArg?: any): U[];
想必很多人都知道答案了,演算过程说一下大概就是,这里还需要了解一些 map 的函数签名,map 第一个参数是回调函数,有三个形参,虽然平时一般不会用到第三个,parseInt 接受的参数有两个,所以实际运行起来的是这样的
[1, 2, 3].map((value, index) => parseInt(value, index));
三次演算过程分别是
-
parseInt(1, 0)
,按十进制处理,返回1
-
parseInt(2, 1)
,基数不可能是1
,返回NaN
-
parseInt(3, 2)
,二进制里面不可能出现3
的,返回NaN
[1, 2, 3].filter(parseInt); //[1]
[1, 2, 3].find(parseInt); // 1
[1, 2, 3].findIndex(parseInt); // 0
filter
的回调函数签名和 map
是一样的,filter
的作用是,过滤掉返回值是假值的函数,find
是找到第一个返回值是真值的 item
,findIndex
的作用是,找到第一个返回是真值的 index
演算过程
-
parseInt(1, 0)
,按十进制处理,返回1
,真值,find
返回1
,findIndex
返回0
, -
parseInt(2, 1)
,基数不可能是1
,返回NaN
,假值,被过滤了 -
parseInt(3, 2)
,二进制里面不可能出现3
的,返回NaN
,假值,被过滤了
这里提一下, find
和 findIndex
是可以用来做短路运算的,碰到 true
,就会退出,可以用来替代 forEach
这种没法通过 return
退出的,但是这样代码语义化就很弱了。
[1,2,3].reduce(parseInt)
// 1
reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: ReadonlyArray) => T, initialValue: T): T;
reduce
的回调参数有四个,一个是上一次循环返回的值,一个是当前值,一个是当前值索引,一个是原数组。这道题里面没有涉及到初始值,所以迭代是从 2
开始的。
两次演算过程分别是:
-
parseInt(1, 2)
, 二进制1
, 转成十进制也是1
,这时候,返回1
,进入下一轮迭代 -
parseInt(1, 3)
, 第二次迭代,上一次迭代的值是1
,当前值是3
, 三进制的1
,返回1
[1, 2, 3].reduceRight(parseInt);
// NaN
reduceRight
的签名和 reduce
一样,不过迭代是从右边开始的。
两次演算过程分别是:
-
parseInt(3, 2)
, 二进制3
, 返回NaN
-
parseInt(NaN, 1)
, 第二次迭代,上一次迭代的值是NaN
,当前值是1
, 基数为1
,返回NaN
[1, 2, 3].some(parseInt); // true
[1, 2, 3].every(parseInt); // false
some
every
的回调函数签名和 map
一样,some
是有真值就返回 true, every
是全部都是真值才返回 true
。
演算过程分别是
-
parseInt(1, 0)
,按十进制处理,返回1
,真值,some
返回true
,运算结束 -
parseInt(2, 1)
,基数不可能是1
,返回NaN
,假值,every
返回false
,运算结束
这里提一下, some
和 every
是可以用来做短路运算的,some
碰到 true
,就会退出, every
碰到 false
就会退出,可以用来替代 forEach
这种没法通过 return
退出的,但是这样代码语义化就很弱了。
[1, 2, 3].forEach(parseInt); // true
// undefined
这道题要是答错了,是真的要给自己一巴掌。。。。
[1, 2, 3].map(parseInt);
有没有经过类型转换?有的。因为 parseInt
会把第一个传进来的参数,如果不是 string
就要转成 string
,这里有个类型转换
写在最后
一个看似很简单的 API
,其实考法还挺多的,如果不仔细了解的话,就不会知道其中的原理。