TL;DR
-
['10', '10', '10'].map(parseInt)
は直感に反する結果[10, NaN, 2]
を返す。 - これはparseIntが省略可能な第2引数を取り、mapがコールバックに3個の引数を渡すため。
- この問題を回避するためには、
['10', '10', '10'].map(e => parseInt(e))
のように匿名関数でラップしてmapからparseIntに第2引数が渡されるのを抑止すれば良い。
問題
JavaScriptで数字文字列があり、これを数値の配列にしたいとします。
'10' // これを
10 // こうしたい
こんなときは標準関数parseIntが使えます。
parseInt('10');
// => 10
さて、今度はJavaScriptで数字文字列の配列があり、これを数値の配列にしたいとします。
['10', '10', '10'] // これを
[ 10 , 10 , 10 ] // こうしたい
配列の全要素に関数を適用するには、Arrayのメソッドmapを使えばいいですね。
['10', '10', '10'].map(parseInt);
// => [10, NaN, 2] // あれ!?
解説
なぜこのような結果が返ってくるのでしょうか?
まずArray.prototyp.mapのコールバックに渡される引数の仕様を見てみましょう。
callbackfn is called with three arguments: the value of the element, the index of the element,
and the object being traversed.(意訳) コールバック関数には3個の引数が渡される。 要素値、インデクス、そして走査する配列オブジェクトだ。
http://www.ecma-international.org/ecma-262/6.0/#sec-array.prototype.map
次にparseIntの仕様を見てみましょう。
parseIntは第1引数に変換する文字列、第2引数に基数を受け取ります。
ただし、第2引数を省略した場合は、下記の動作をします。
If radix is undefined or 0, it is assumed to be 10 except when the number begins with the code unit pairs 0x or 0X,
in which case a radix of 16 is assumed.(意訳) 基数が未指定か0の場合、10進数とみなして評価される。ただし、文字列が0xまたは0Xから始まる場合は16進数とみなされる。
http://www.ecma-international.org/ecma-262/6.0/#sec-parseint-string-radix
以上を踏まえて、先のコードがどう動くか考えてみます。
['10', '10', '10'].map(parseInt);
- 1番目の要素:
parseInt('10', 0, ['10', '10', '10'])
=> '10'を10進数とみなし10
- 2番目の要素:
parseInt('10', 1, ['10', '10', '10'])
=> 不正な基数1なのでNaN
- 3番目の要素:
parseInt('10', 2, ['10', '10', '10'])
=> '10'を2進数のイチゼロとみなし2
実に「正しく」変換されているのが分かりますね。
さすがJavaScriptさんやでー。
解決
mapのコールバックとしてparseIntを直接渡すのではなく、匿名関数でラップします。
['10', '10', '10'].map(e => parseInt(e, 10)); // (1) 明示的に10進数とみなして変換
['10', '10', '10'].map(e => parseInt(e, 0)); // (2) 自動判定で変換(この配列の場合はすべて10進数)
['10', '10', '10'].map(e => parseInt(e)); // (3) 同上
上記のいずれも [10, 10, 10]
を返します。