最近频繁出现的 `parseInt` 和 `Array` 原型方法的题目

首先看下 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 是找到第一个返回值是真值的 itemfindIndex 的作用是,找到第一个返回是真值的 index

演算过程

  • parseInt(1, 0),按十进制处理,返回 1,真值,find 返回 1findIndex 返回 0
  • parseInt(2, 1),基数不可能是 1,返回 NaN,假值,被过滤了
  • parseInt(3, 2),二进制里面不可能出现 3 的,返回 NaN,假值,被过滤了

这里提一下, findfindIndex 是可以用来做短路运算的,碰到 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,运算结束

这里提一下, someevery 是可以用来做短路运算的,some 碰到 true,就会退出, every 碰到 false 就会退出,可以用来替代 forEach 这种没法通过 return 退出的,但是这样代码语义化就很弱了。

[1, 2, 3].forEach(parseInt); // true
// undefined

这道题要是答错了,是真的要给自己一巴掌。。。。

[1, 2, 3].map(parseInt); 有没有经过类型转换?有的。因为 parseInt 会把第一个传进来的参数,如果不是 string 就要转成 string,这里有个类型转换

写在最后

一个看似很简单的 API,其实考法还挺多的,如果不仔细了解的话,就不会知道其中的原理。

你可能感兴趣的:(前端,javascript,typescript)