最近頻繁出現的 `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<U>(callbackfn: (value: T, index: number, array: ReadonlyArray<T>) => 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>) => 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,其實考法還挺多的,如果不仔細瞭解的話,就不會知道其中的原理。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章