无意中发现了JavaScript里面Array.map的一个bug?

map方法里面的function无论return什么,得到的结果总是[undefined, undefined]window.console(new Array(2).map(function(a){return 1}))
关注者
319
被浏览
37619

感谢题主,这个问题很有意思,如果你不问我都不知道 JavaScript 还有这个坑。

我可以给你解释一下为什么「这不是一个 Bug」:

虽然,我们有

new Array(2).length           // 2
new Array(2)[0] === undefined // true
new Array(2)[1] === undefined // true

可是,new Array(2) 与 [undefined, undefined] 是不同的。


对于 [undefined, undefined],我们总是有

[undefined, undefined].map(e => 1)  // [1, 1]

但是对于 new Array(2),它实际上返回的是一个长度为 2,里面什么都没有的数组。


要怪就怪 Chrome,在 Chrome(实测 58、60)下写得非常不清楚

new Array(2)             // (2) [undefined x 2]
new Array(2).map(e => 1) // (2) [undefined x 2]
[undefined, undefined]   // (2) [undefined, undefined]

是不是看起来差不多?我已经提了个 issue 给 Chrome: 732021 - new Array(len) should not log as (len) [undefined x len] - chromium - Monorail,[undefined x 2] 是什么鬼,简直是坑人嘛……


根据 ECMA-262 6th edition,即 ES6 规范 22.1.1.2 小节 Array (len),其实 Array(len) 仅仅就是给新数组设了个 length 属性而已,其他啥也没干……而对于 JS 数组的索引吧,你就算是数组越界了,也都是返回 undefined:

new Array(2)[999] === undefined // true

那为什么这个能影响到 Array.prototype.map() 呢。根据规范 22.1.3.15 小节 Array.prototype.map ( callbackfn [ , thisArg ] ),map 函数会先检查 HasProperty。因为你要 map,首先这个元素/属性要存在啊。可是:

new Array(2).hasOwnProperty(0) // false
[undefined, undefined].hasOwnProperty(0) // true

所以,你传给 map 的 callback 其实根本就没执行,不信你可以试试

new Array(2).map(e => console.log('hey~~'))  

会发现啥也没 log 出来。


但是这个问题在其他浏览器里就好多啦。

在 Safari 10 下

new Array(2)             // [](2)
new Array(2).map(e => 1) // [](2) 
[undefined, undefined]   // [undefined, undefined] (2)

Firefox Dev Edition 下:

new Array(2)             // Array [ <2 empty slots> ]
new Array(2).map(e => 1) // Array [ <2 empty slots> ]
[undefined, undefined]   // Array [ undefined, undefined ]

啊哈,这下就非常清楚了是不是?


但至于 JavaScript 中的 new Array(len) 为什么要这么设计?

讲道理啊,问 @贺师俊


---

其他答案有提到 Array() 创建的是 JS 语境中的「稀疏数组」(Sparse Array):

a = new Array(2); a[1] = 1; a;  // (2) [undefined × 1, 1]

当然其实在 JS 里随便创建个数组都可以是「稀疏」的……(因为实际上就是 Object 嘛)

b = []; b[4] = 5; b[9] = 10;
b;  // (10) [undefined × 4, 5, undefined × 4, 10]

而 JS 对于这类数组,由于上述规范的定义,遍历的时候会跳过空的地方:

a.map(e => 2*e)  //(2) [undefined × 1, 2]

我觉得从这个角度去理解和记忆 JavaScript 的这个行为还蛮去。


但也需要说明的是,Sparse Array 并没有一个那么统一的定义。

比如你也会看到 "A sparse array is simply an array most of whose entries are zero (or null, or some other default value); " 这样的定义,那么 Java 中 new Type[len] 采用默认值(null, 0, 0.0, false)填充出的数组是不是也算稀疏数组呢?可是 Android 就不买账又给 Android 搞了一个更类似上述 JavaScript 行为的 SparseArray,可以在省内存时用。

同样,JS 引擎对于稀松数组是用字典保存的,能省一些空间,但是在索引方面肯定是比 Full Array 吃亏了。所以 JavaScript 不把 new Array 设计成全用 undefined 填满,不但有点反直觉,我暂时也没有想到什么其他好处。


---

评论区里说 Edge 也有这个坑,于是我也去提了个 issue:

new Array(len) should not log as [undefind, ...]

Edge 的 issue tracker 也是一堆 bug……跟知乎编辑器有得一拼 :(