爲什麼 ['1', '7', '11'].map(parseInt) 的結果是 [1, NaN, 3]?

原文:https://pantao.parcmg.com/press/parseint-mystery.html

Javascript 總是以超自然的方式執行我們的代碼,這是一件很神奇的事情,如果不行的話,思考一下 ['1', '7', '11'].map(parseInt) 的結果是什麼?你以爲會是 [1, 7, 11] 嗎?我都這麼問了,那肯定不是:

['1', '7', '11'].map(parseInt)
// 輸出:(3) [1, NaN, 3]

要理解爲什麼爲會這裏,首先需要了解一些 Javascript 概念,如果你是一個不太喜歡閱讀長文的人,那直接跳到最後看結論吧

真值與假值

請看下面示例:

if (true) {
  // 這裏將啓動都會被執行
} else {
  // 這裏永遠都不會被執行
}

在上面的示例中, if-else 聲明爲 true,所以 if-block 會一直都執行,而 else-block 永遠都會被忽略掉,這是個很容易理解的示例,下面我們來看看另一個例子:

if ("hello world") {
  // 這裏會被執行嗎?
  console.log("條件判斷爲真");
} else {
  // 或者會執行這裏嗎?
  console.log("條件判斷爲假");
}

打開你的開發者工具 console 面板,執行上面的代碼,你會得到看到會打印出 條件判斷爲真,也就是說 "hello world" 這一個字符串被認爲是真值

在 JavaScript 中,truthy(真值)指的是在布爾值上下文中,轉換後的值爲真的值。所有值都是真值,除非它們被定義爲 假值(即除 false、0、""、null、undefined 和 NaN 以外皆爲真值)。

引用自:Mozilla Developer: Truthy(真值)

這裏一定要劃重點:在布爾上下文中,除 false0""(空字符串)、nullundefined 以及 NaN 外,其它所有值都爲真值

基數

0 1 2 3 4 5 6 7 8 9 10

當我們從 0 數到 9,每一個數字的表示符號都是不一樣的(0-9),但是當我們數到 10 的時候,我們就需要兩個不同的符號 10 來表示這個值,這是因爲,我們的數學計數系統是一個十進制的。

基數,是一個進制系統下,能使用僅僅超過一個符號表示的數字的最小值,不同的計數進制有不同的基數,所以,同一個數字在不同的計數體系下,表示的真實數據可能並不一樣,我們來看一下下面這張在十進制、二進制以及十六進制不同值在具體表示方法:

十進制(基數:10) 二進制(基數:2) 十六進制(基數:16)
0 0 0
1 1 1
2 10 2
3 11 3
4 100 4
5 101 5
6 110 6
7 111 7
8 1000 8
9 1001 9
10 1010 A
11 1011 B
12 1100 C
13 1101 D
14 1110 E
15 1111 F
16 10000 10
17 10001 11
18 10002 12

你應該已經注意到了, 11 在上表中,總共出現了三次。

函數的參數

在 Javascript 中,一個函數可以傳遞任何多個數量的參數,即使調用時傳遞的數量與定義時的數量不一致。缺失的參數會以 undefined 作爲實際值傳遞給函數體,然後多餘的參數會直接被忽略掉(但是你還是可以在函數體內通過一個類數組對象 arguments 訪問到)。

function sum(a, b) {
  console.log(a);
  console.log(b);
}

sum(1, 2);    // 輸出:1, 2
sum(1);       // 輸出:1, undefined
sum(1, 2, 3); // 輸出:1, 2

map()

快要有結果了。

map() 是一個存在於數組原型鏈上的方法,它將其數組實例中的每一個元素,傳遞給它的第一個參數(在本文最開始的例子中就是 parseInt),然後將每一個返回值都保存到同一個新的數組中,遍歷完所有元素之後,將新的包含了所有結果的數組返回。

function multiplyBy3 (number) {
  return number * 3;
}

const result = [1, 2, 3, 4, 5].map(multiplyBy3);

console.log(result); // 輸出:[3, 6, 9, 12, 15]

現在,假想一下,我們想使用 map() 將一個數組中的每一個元素都打印到控制檯,我可以直接將 console.log 函數傳遞給 map() 方法:

[1, 2, 3, 4, 5].map(console.log);

輸出:

1 0 (5) [1, 2, 3, 4, 5]
2 1 (5) [1, 2, 3, 4, 5]
3 2 (5) [1, 2, 3, 4, 5]
4 3 (5) [1, 2, 3, 4, 5]
5 4 (5) [1, 2, 3, 4, 5]

是不是感覺有點神奇了?爲什麼會這樣?來分析一下上面輸出的內容都有些什麼?

  • 第一列:這個看上去跟我們想要的輸出是一致的
  • 第二列:這個是一個從 0 開始遞增的數字
  • 第三列:總是 (5) [1, 2, 3, 4, 5]

再來看看下面這段代碼:

[1, 2, 3, 4, 5].map((value, index, array) => console.log(value, index, array));

在控制檯上試試執行上面這行代碼,你會發現,它與 [1, 2, 3, 4, 5].map(console.log); 輸出的結果是完全一致的,**總是會被我們忽略的一點是,map() 方法會將三個參數傳遞傳遞給它的函數,分別是 currentValue(當前值)、currentIndex(當前索引)以及 array 本身,這就是,上面爲什麼結果有三列的原因,如果我們只是想將每一個值打印,那麼需要像下面這樣寫:

[1, 2, 3, 4, 5].map((value) => console.log(value));

回到最開始的問題

現在讓我們來回顧一下本文最開始的問題:

爲什麼 ['1', '7', '11'].map(parseInt) 的結果是 [1, NaN, 3]

來看看 parseInt() 是一個什麼樣的函數:

parseInt(string, radix)將一個字符串 string 轉換爲 radix 進制的整數, radix 爲介於 2-36 之間的數。

詳情:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/parseInt

這裏還有一條需要注意的是,雖然我們一般都是使用 parseInt('11') 這樣的調用方式,認爲是將 11 按十進制轉換成數字,但是,請在使用的時候,永遠都添加 radix 參數,因爲, radix10 並不保證永遠有效(這並不是規範的一部分)。

那麼看看下面這些的輸出:

parseInt('11');             // 輸出:11
parseInt('11', 2);          // 輸出:3
parseInt('11', 16);         // 輸出:17

parseInt('11', undefined);  // 輸出:11 (radix 是假值)
parseInt('11', 0);          // 輸出:11 (radix 是假值)

現在,讓我們一步一步來看 ['1', '7', '11'].map(parseInt) 的整個執行過程,首先,我們可以將這一行代碼,轉換爲完整的版本:

['1', '7', '11'].map((value, index, array) => parseInt(value, index, array))

根據前面的知識,我們應該知道, parseInt(value, index, array) 中的 array 會被忽略(並放入 arguments 對象中。

那麼,完整的代碼就是下面這樣的:

['1', '7', '11'].map((value, index, array) => parseInt(value, index))
  • 遍歷到第一個元素時:

    parseInt('1', 0);

    radix0,是一個假值,在我們的控制檯中,一般將使用 10 進制,所有,得到結果 1

  • 遍歷到第二個元素時:

    parseInt('7', 1)

    在一個 1 進制系統中,7 是不存在的,所以得到結果 NaN(不是一個數字)

  • 遍歷到第三個元素時:

    parseInt('11', 2)

    在一個 2 進制系統中,11 就是進十制的 3

總結

['1', '7', '11'].map(parseInt) 的結果是 [1, NaN, 3] 的原因是因爲,map() 方法是向傳遞給他的函數中傳遞三個參數,分別爲當前值,當前索引以及整個數組,而 parseInt 函數接收兩個參數:需要轉換的字符串,以及進制基數,所以,整個語句可以寫作:['1', '7', '11'].map((value, index, array) => parseInt(value, index, array))arrayparseInt 捨棄之後,得到的結果分別是:parseInt('1', 0)parseInt('7', 1) 以及 parseInt('11', 2),也就是上面看到的 [1, NaN, 3]

正確的寫法應該是:

['1', '7', '11'.map(numStr => parseInt(numStr, 10));
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章