JavaScript 中 Array#map 和 parseInt 组合使用的陷阱

2012-02-11 21:15

JavaScript 中 Array#map 和 parseInt 组合使用的陷阱

by Xueqiao Xu

at 2012-02-11 13:15:05

original http://typedef.me/2012/02/11/javascript-array-map-parseint-pitfall

将一个形如 ['1', '2', '3'] 的数组转换为 [1, 2, 3] ,很多人也许会不加思索的写出下面的代码:

['1', '2', '3'].map(parseInt);

上面这个代码看上去十分正确,而且非常优美,没有任何多余的噪音。但其返回结果却令人吃惊:

> ['1', '2', '3'].map(parseInt);
[ 1, NaN, NaN ]

“WTF !!!!为什么第一个元素是正确的,而后面的会是 NaN ?”

然后继续实验:

> ['1', '1', '1', '1'].map(parseInt);
[ 1, NaN, 1, 1 ]

“喂喂,中间的那位 NaN 同学,你走错片场了吧。”

究其原因,其实是由两部分组成的:

  1. map 的回调函数的第二个参数为数组下标
  2. parseInt 的第二个参数为转换进制

看到上面这两点,熟练 js 的同学应该就会恍然大悟了。如果还没明白的话也没关系,下面是详细的解释:

Array#map

首先,map 方法所期待的回调函数在调用时接受三个参数,分别为:

  1. 当前元素的值
  2. 当前元素的索引(下标)
  3. 被遍历的数组

一个典型的 map 实现如下:

Array.prototype.map = function map(fun) {
  var length = this.length >>> 0;
  var result = Array(length);
  var thisp = arguments[1];

  for (var i = 0; i < length; i++) {
    if (i in this)
      result[i] = fun.call(thisp, this[i], i, this);
  }
  return result;
};

调用示例:

['a', 'b', 'c'].map(function(v, i, a) {
  console.log(v, i, a);
});
// prints
// a 0 [ 'a', 'b', 'c' ]
// b 1 [ 'a', 'b', 'c' ]
// c 2 [ 'a', 'b', 'c' ]

parseInt

parseInt 可以接受两个参数,其中第二个参数(可选)为转换的进制

例如,以 8 进制解析 12

> parseInt('12', 8)
10

而当第二个参数不存在或为0时:

  1. 如果字符串以 0x0X 开头,则以 16 进制解析
  2. 如果以 0 开头,则以 8 进制解析
  3. 否则以 10 进制解析
  4. 解析失败返回 NaN

因此,当我们组合使用 mapparseInt 的时候,每个元素的下标成为了解析它时的进制:

['1', '2', '3'].map(function(v, i) {
  console.log('parseInt("' + v + '", ' + i + ') gives ' + parseInt(v, i));
});
// prints
// parseInt("1", 0) gives 1
// parseInt("2", 1) gives NaN
// parseInt("3", 2) gives NaN

注意上面结果的最后两项,以1进制来解析 "2" 和以2进制来解析 "3" 都是失败的,因此返回 NaN

正确的做法应当为:

['1', '2', '3'].map(function(v) { // 只传入元素的值
  return parseInt(v, 10); // 指定以 10 为底,这是一个好习惯
})

至此,原因应当相当清晰了。同时,也当记住,除了 parseInt 外,任何可以接受两个或两个以上参数的函数在配合 map 使用的时候都应当多加注意。