JavaScript“new Array(n)”和“Array.prototype.map”怪异

JavaScript “new Array(n)” and “Array.prototype.map” weirdness

我在firefox-3.5.7/firebug-1.5.3和firefox-3.6.16/firebug-1.6.2中观察到了这一点。

当我启动Firebug时:

1
2
3
4
5
6
7
8
9
10
11
12
    >>> x = new Array(3)
    [undefined, undefined, undefined]
    >>> y = [undefined, undefined, undefined]
    [undefined, undefined, undefined]

    >>> x.constructor == y.constructor
    true

    >>> x.map(function(){ return 0; })
    [undefined, undefined, undefined]
    >>> y.map(function(){ return 0; })
    [0, 0, 0]

这是怎么回事?这是一个bug,还是我误解了如何使用new Array(3)


第一个例子

1
x = new Array(3);

创建具有未定义指针的数组。

第二个创建一个带有指向3个未定义对象的指针的数组,在这种情况下,它们自己的指针不是未定义的,只有它们指向的对象。

1
2
3
y = [undefined, undefined, undefined]
// The following is not equivalent to the above, it's the same as new Array(3)
y = [,,,];

因为map是在数组中对象的上下文中运行的,所以我相信第一个map在第二个map运行时根本无法运行函数。


我有一个任务,我只知道数组的长度,需要转换项目。我想这样做:

1
let arr = new Array(10).map((val,idx) => idx);

要快速创建这样的数组:

1
[0,1,2,3,4,5,6,7,8,9]

但它不起作用,因为:看乔纳森·洛诺夫斯基的回答,上面有几个答案。

解决方案可以是使用array.prototype.fill()用任何值(即使未定义)填充数组项

1
let arr = new Array(10).fill(undefined).map((val,idx) => idx);

1
console.log(new Array(10).fill(undefined).map((val, idx) => idx));

更新

另一种解决方案可能是:

1
let arr = Array.apply(null, Array(10)).map((val, idx) => idx);

1
console.log(Array.apply(null, Array(10)).map((val, idx) => idx));


有了ES6,你就可以轻松快捷地完成[...Array(10)].map((a, b) => a)


ES6溶液:

1
[...Array(10)]

但不能在typescript(2.3)上工作


数组是不同的。区别在于,new Array(3)创建了一个长度为三个但没有属性的数组,而[undefined, undefined, undefined]创建了一个长度为三个和三个属性的数组,分别称为"0"、"1"和"2",每个属性的值为undefined。使用in操作符可以看到不同之处:

1
2
"0" in new Array(3); // false
"0" in [undefined, undefined, undefined]; // true

这源于一个稍微令人困惑的事实,即如果您试图获取javascript中任何本机对象的不存在属性的值,它将返回undefined(而不是像您试图引用不存在的变量时所发生的那样抛出错误),这与之前将该属性明确设置为edo时得到的结果相同。CX1〔3〕。


map的MDC页面:

[...] callback is invoked only for indexes of the array which have assigned value; [...]

[undefined]实际上在索引上应用setter,以便map迭代,而new Array(1)只是用默认值undefined初始化索引,所以map跳过它。

我相信这对于所有迭代方法都是一样的。


我认为最好的解释方法是看看Chrome处理它的方式。

1
2
3
4
>>> x = new Array(3)
[]
>>> x.length
3

所以实际发生的是new array()返回的是一个长度为3但没有值的空数组。因此,当您在一个技术上为空的数组上运行x.map时,不需要设置任何内容。

火狐只是用undefined来"填充"那些空槽,尽管它没有值。

我不认为这是一个明确的错误,只是一个糟糕的方式来表示正在发生的事情。我认为Chrome的"更正确",因为它表明数组中实际上没有任何内容。


在ECMAScript第6版规范中。

new Array(3)只定义length属性,不定义{length: 3}等索引属性。请参见https://www.ecma-international.org/ecma-262/6.0/index.html sec array len step 9。

[undefined, undefined, undefined]将定义索引属性和长度属性,如{0: undefined, 1: undefined, 2: undefined, length: 3}。参见https://www.ecma-international.org/ecma-262/6.0/index.html sec运行时语义数组累计ElementList步骤5。

方法数组的mapeverysomeforEachslicereducereduceRightfilter通过HasProperty内部方法检查索引属性,因此new Array(3).map(v => 1)不调用回调。

有关详细信息,请参阅https://www.ecma-international.org/ecma-262/6.0/index.html sec-array.prototype.map

如何修复?

1
2
3
4
5
6
7
8
9
10
11
let a = new Array(3);
a.join('.').split('.').map(v => 1);

let a = new Array(3);
a.fill(1);

let a = new Array(3);
a.fill(undefined).map(v => 1);

let a = new Array(3);
[...a].map(v => 1);

刚刚碰到这个。使用Array(n).map肯定很方便。

Array(3)大致产生{length: 3}

[undefined, undefined, undefined]创建编号属性:江户十一〔29〕号。

map()实现只对定义的属性起作用。


如果您这样做是为了方便地用值填充数组,由于浏览器支持的原因不能使用fill,并且确实不想执行for循环,那么您也可以执行x = new Array(3).join(".").split(".").map(...,这将为您提供一个空字符串数组。

我不得不说得很难看,但至少问题和意图都清楚地传达了出来。


不是虫子。数组构造函数就是这样定义的。

来自MDC:

When you specify a single numeric parameter with the Array constructor, you specify the initial length of the array. The following code creates an array of five elements:

1
var billingMethod = new Array(5);

The behavior of the Array constructor depends on whether the single parameter is a number.

.map()方法只包括数组中明确指定了值的迭代元素。即使是对undefined的显式赋值,也会导致一个值被认为有资格包含在迭代中。这似乎很奇怪,但本质上是对象上的显式undefined属性和缺少的属性之间的区别:

1
2
var x = { }, y = { z: undefined };
if (x.z === y.z) // true

对象x没有名为"z"的属性,而对象y有。然而,在这两种情况下,物业的"价值"似乎都是undefined。在数组中,情况类似:EDOCX1的值(8)隐式地执行从零到length - 1的所有元素的值赋值。因此,当对使用数组构造函数和数字参数新建的数组调用时,.map()函数将不执行任何操作(不会调用回调)。


这里有一个简单的实用方法作为解决方法:

简单映射

1
2
3
4
5
6
7
8
9
10
11
12
13
function mapFor(toExclusive, callback) {
    callback = callback || function(){};
    var arr = [];
    for (var i = 0; i < toExclusive; i++) {
        arr.push(callback(i));
    }
    return arr;
};

var arr = mapFor(3, function(i){ return i; });
console.log(arr); // [0, 1, 2]
arr = mapFor(3);
console.log(arr); // [undefined, undefined, undefined]

完整的例子

下面是一个更完整的示例(带有健全性检查),它还允许指定可选的起始索引:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
function mapFor() {
var from, toExclusive, callback;
if (arguments.length == 3) {
    from = arguments[0];
    toExclusive = arguments[1];
    callback = arguments[2];
} else if (arguments.length == 2) {
    if (typeof arguments[1] === 'function') {
        from = 0;
        toExclusive = arguments[0];
        callback = arguments[1];
    } else {
        from = arguments[0];
        toExclusive = arguments[1];
    }
} else if (arguments.length == 1) {
    from = 0;
    toExclusive = arguments[0];
}

callback = callback || function () {};

var arr = [];
for (; from < toExclusive; from++) {
    arr.push(callback(from));
}
return arr;
}

var arr = mapFor(1, 3, function (i) { return i; });
console.log(arr); // [1, 2]
arr = mapFor(1, 3);
console.log(arr); // [undefined, undefined]
arr = mapFor(3);
console.log(arr); // [undefined, undefined, undefined]

倒计时

操作传递给回调的索引允许向后计数:

1
2
3
4
5
var count = 3;
var arr = arrayUtil.mapFor(count, function (i) {
    return count - 1 - i;
});
// arr = [2, 1, 0]

在Chrome中,如果我使用new Array(3),我会得到[],所以我猜你遇到了浏览器错误。