為什麼 ["1", "2", "3"].map(parseInt) 返回 [1,NaN,NaN] ---續
最近碰到了[‘1’,’2’,’3’].map(parseInt)這種看似不起眼陷阱卻極大的問題。
這乍一看,感覺應該會輸出[1,2,3]。但是,實際上並不是我們想的這樣。你可以現在開啟console,看看輸出的結果。
出乎意料結果竟然是[1,NaN,NaN].
至於為什麼是這樣,下面一步一步的解釋。
parseInt()
parseInt是JS的一個內建函式,它可以將字元創解析成一個數值表示式並將該數值返回。呼叫的時候如下:
var n = parseInt('123')
- 1
函式返回的結果是123 ,並賦值給變數n。這裡應該沒什麼問題。
當然,如果傳入pasreInt()引數並不能被解析成數值表示式,那麼函式執行後將返回 NaN。NaN
var m = parseInt('xyz)
- 1
此時m的值應該就是NaN。
Array.prototype.map
map 是ECMAScript 5 的一個內建數值方法.map把函式作為它的引數。map遍歷陣列中所有的元素,並且為每個元素呼叫一次這個傳入map中的函式,當前元素作為引數傳入該函式。map函式呼叫完畢之後將返回的結果存入一個新陣列中。看下面這個例子:
[1,2,3].map(function(value){
return value+1
})
- 1
- 2
- 3
很好理解,最後的結果的應該是[2,3,4].這種呼叫應該是很常見的。但是有時將已經存在的函式作為引數傳入map是無效的啊,比如parseInt.
清楚了parsenInt和map的基本用法之後,再來看看比較容易忽視的問題。
解決方法
現在來看看ECMAScript 5 中parseInt 的用法。有會發現它可以接受兩個引數。第一個引數是要被解析的字串,第二個引數是要解析成數值的基礎。所以,
parseInt('ffff',16)
返回的結果是65535,然而
parseInt('ffff',8)
- 1
返回的結果是NaN,因為“ffff”並不能解析成一個八進位制的數值。如果第二個引數省略了或者是0,parseInt預設為是10進位制 所以
parseInt("12",10) ;
parseInt("12") ;
parseInt("12",0 ) ;
- 1
- 2
- 3
都會返回數值12。
現在看看map 的定義。map指定傳入的函式引數作為callbackfn.規範裡指出:“callbackfn呼叫時需要傳入三個引數:元素的值,元素的索引和正在被遍歷的物件”。仔細體會一下,原本我們以為對parseInt的三次呼叫是這樣:
parseInt('1')
parseInt('2')
parseInt('3')
- 1
- 2
- 3
然而實際上是這樣呼叫的:
parseInt('1',0,theArray);
parseInt('2',1,theArray);
parseInt('3',2,theArray);
- 1
- 2
- 3
這裡theArray 就是原始陣列[‘1’,’2’,’3’]。
JS函式一般會自動忽視多餘的引數,parseInt僅僅需要兩個引數,所以我們並不需要擔心這個theArray引數對parseInt的影響。
重點來了,第二個引數是如何影響parseInt的
在第一次呼叫時,parseInt的第二個引數是0,上面已經說了,所以第一呼叫
parseInt('1',0)
返回1.
第二次呼叫第二個引數是1,也是說1作為數值的基礎。規範裡說的很清楚了,如果基礎是非0或者小於2,函式都不會查詢字串直接返回NaN。
第三次呼叫時,2作為基數。這就意味著字串將被解析成位元組數,也就是僅僅包含數值0和1。parseInt的規範第十一步指出,它僅嘗試分析第一個字元的左側,這個字元還不是要求基數的有效數字。這個字串的第一個字元是“3”,它並不是基礎基數2的一個有效數字。所以這個子字串將被解析為空。第十二步說了:如果子字串被解析成空了,函式將返回為NaN。
所以這裡的結果就應該是[1,NaN,NaN].
這裡問題所在就是容易忽視parseInt是需要兩個引數的。map中有三個引數。所以這裡結合起來,就導致了上面問題。 換個方式寫:
['1','2','3'].map(function(value){
return parseInt(value)
})
- 1
- 2
- 3
這樣就不會出錯。
當然,我們也可以寫:
['1','2','3'].map(Number)
- 1