JS 中為啥 「'1', '7', '11'」.map(parseInt) 返回 「1, NaN, 3」

JavaScript Chrome 啟迪雲Tuscloud 2019-06-27

譯者:前端小智 原文:https://medium.com/dailyjs/parseint-mystery-7c4368ef7b21

為了保證可讀性,本文采用音譯而非直譯。


JS 中為啥 「'1', '7', '11'」.map(parseInt) 返回 「1, NaN, 3」


Javascript 一直是神奇的語言。 不相信我? 嘗試使用 map和 parseInt將字符串數組轉換為整數。打開 Chrome 的控制檯(F12),粘貼以下內容,然後按回車,查看輸出結果:

['1', '7', '11'].map(parseInt);

我們得到的不是一個整數數組 [1,7,11],而是 [1,NAN,3],要了解究竟發生了什麼,我們首先要討論一些Javascript概念。

真值(truthy) & 虛值(falsy)

以下是 JS 中一個簡單的 if-else語句:

if

(
true
)

{

// this always runs
}

else

{

// this never runs
}

在上例中, if 條件為 true,因此總是執行 if塊,忽略 else塊。這是一個簡單的例子,因為 true是一個布爾值。如果我們把非布爾值作為條件呢 ?

if

(
"hello world"
)

{
console
.
log
(
"Condition is truthy"
);
}

else

{
console
.
log
(
"Condition is falsy"
);
}

打開控制檯並運行上述代碼,會打印 Conditionistruthy,說明條件 "hello world" 為真(true)值。

在 JavaScript 中,Truthy (真值)指的是在 布爾值 上下文中轉換後的值為真的值。所有值都是真值,除非它們被定義為 falsy (即除了 false, 0, "", null, undefined 和 NaN 外)。

falsy(虛值)是在 Boolean 上下文中已認定可轉換為‘假‘的值。

JS中的對象不是真值就是虛值。

令人困惑的是,這意味著字符串 “false”,字符串 “0”,空對象 {}和空數組 []都是真的。 使用使用 Boolean 方法來驗證,如 Boolean("0")。

出於我們的目的,接下來只要記住 0是假的就行了。

基數

在數學上,基數(cardinal number)是集合論中刻畫任意集合大小的一個概念。兩個能夠建立元素間一一對應的集合稱為互相對等集合。例如3個人的集合和3匹馬的集合可以建立一一對應,是兩個對等的集合。

0

1

2

3

4

5

6

7

8

9

10

當我們從 0數到 9時,每個數字(0-9)都有不同的符號,但是當我們數到 10時,我們需要兩個不同的符號( 1和 0)來表示這個數字。這是因為我們的十進制計數系統的基數是 10。

基數是最小的數字,只能由多個符號表示。 不同的計數系統具有不同的基數,因此,相同的數字在不同的計數系統中可以表示不同的數字。

十進制

二進制

十六進制
RADIX
=
10
RADIX
=
2
RADIX
=
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

看上表,可以看到相同的數字 11在不同的計數系統中可以表示不同的數字。如果基數是 2,那麼它表示數字為 3。如果基數是 16,那麼它指的是數字 17。

你可能已經注意到,在我們的示例中,當輸入為 11時, parseInt返回 3,這對應於上表中的二進制列。

函數參數

JS 中函數調用,我們可以傳入任意的參數,即使它們不等於聲明時的函數參數的數量。缺少的參數被視為 undefined 的,並且會忽略額外的參數,但會保存在類似數組的 arguments對象中。

function
foo
(
x
,
y
)

{
console
.
log
(
x
);
console
.
log
(
y
);
}
foo
(
1
,

2
);

// 打印 1, 2
foo
(
1
);

// 打印 1, undefined
foo
(
1
,

2
,

3
);

// 打印 1, 2

map()

map是 Es6 中新出的一個數組方法,它是一個高階函數,通過傳入一個函數進行邏輯操作,並返回一個數組, 例如,以下代碼將數組中的每個元素乘以 3:

function
multiplyBy3
(
x
)

{

return
x
*

3
;
}
const
result
=

[
1
,

2
,

3
,

4
,

5
].
map
(
multiplyBy3
);
console
.
log
(
result
);

// logs [3, 6, 9, 12, 15];

現在,將 console.log作為參數傳給 map,來打印數組的元素:

[
1
,

2
,

3
,

4
,

5
].
map
(
console
.
log
);
等價於

[
1
,

2
,

3
,

4
,

5
].
map
((
val
,
index
,
array
)

=>

console
.
log
(
val
,
index
,
array
));


JS 中為啥 「'1', '7', '11'」.map(parseInt) 返回 「1, NaN, 3」


所以 map 回調方法中會傳入三個參數,分別是 當前遍歷的項,當前索引,及遍歷的整個數組。

原因

ParseInt有兩個參數: string和 radix。 如果提供的基數是虛值,則默認情況下,基數設置為 10。

parseInt
(
'11'
);

=>

11
parseInt
(
'11'
,

2
);

=>

3
parseInt
(
'11'
,

16
);

=>

17
parseInt
(
'11'
,

undefined
);

=>

11

(
radix
is
falsy
)
parseInt
(
'11'
,

0
);

=>

11

(
radix
is
falsy
)

現在一步一步解析開頭的事例。

[
'1'
,

'7'
,

'11'
].
map
(
parseInt
);

=>

[
1
,

NaN
,

3
]
// 第一次迭代: val = '1', index = 0, array = ['1', '7', '11']
parseInt
(
'1'
,

0
,

[
'1'
,

'7'
,

'11'
]);

=>

1

因為 0是虛值,基數設置為默認值 10。 parseInt()只接受兩個參數,因此忽略了第三個參數 ['1'、'7'、'11']。以10為基數的字符串 “1”表示數字 1。

 
// 第二次迭代: val = '7', index = 1, array = ['1', '7', '11']
parseInt
(
'7'
,

1
,

[
'1'
,

'7'
,

'11'
]);

=>

NaN

在基數 1系統中,符號 “7”不存在。與第一次迭代一樣,忽略最後一個參數。因此, parseInt()返回 NaN。

 
// Third iteration: val = '11', index = 2, array = ['1', '7', '11']
parseInt
(
'11'
,

2
,

[
'1'
,

'7'
,

'11'
]);

=>

3

在基數 2(二進制)系統中,符號 “11”表示數字 3。

至此原因已經明瞭了。 有興趣可以試著寫下下列的打印結果:

[
'1'
,

'7'
,

'11'
].
map
(
numStr
=>
parseInt
(
numStr
));

我是小智,公眾號「大遷世界」作者,對前端技術保持學習愛好者。我會經常分享自己所學所看的乾貨,在進階的路上,共勉!