JavaScript &;new Array(n)&;和“;Array.prototype.map"不可思议

我在Firefox-3.5.7/Firebug-1.5.3和Firefox-3.6.16/Firebug-1.6.2中观察到这一点

当我启动Firebug时:

var x = new Array(3)
console.log(x)
// [undefined, undefined, undefined]


var y = [undefined, undefined, undefined]
console.log(y)
// [undefined, undefined, undefined]


console.log( x.constructor == y.constructor) // true


console.log(
x.map(function() { return 0; })
)
// [undefined, undefined, undefined]


console.log(
y.map(function() { return 0; })
)
// [0, 0, 0]

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

91341 次浏览

看来第一个例子

x = new Array(3);

创建一个长度为3但没有任何元素的数组,因此不会创建索引[0]、[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在索引/属性列表上运行,而不是在设置的长度上运行,所以如果没有创建索引/属性,它将不会运行。

不是bug。这就是Array构造函数的工作方式。

争取民主变革运动:

当您使用Array构造函数指定单个数值形参时,您将指定数组的初始长度。下面的代码创建了一个包含五个元素的数组:

var billingMethod = new Array(5);

Array构造函数的行为取决于单个参数是否为数字。

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

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

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

map的MDC页面:

[…callback仅对赋值的数组索引调用;[…]

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

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

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

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

因此,实际发生的情况是new Array()返回一个长度为3的空数组,但没有值。因此,当你在一个从技术上讲空数组上运行x.map时,没有什么要设置的。

Firefox只是用undefined“填充”这些空槽,即使它没有值。

我不认为这是一个明显的错误,只是一种表现正在发生的事情的糟糕方式。我想Chrome的是“更正确”,因为它显示数组中实际上没有任何东西。

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

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

这源于一个有点令人困惑的事实,如果你试图在JavaScript中获取任何原生对象的不存在属性的值,它会返回undefined(而不是抛出一个错误,当你试图引用一个不存在的变量时发生),这与之前显式地将属性设置为undefined所得到的结果相同。

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

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

[undefined, undefined, undefined]创建编号属性 {0: undefined, 1: undefined, 2: undefined, length: 3} . < / p >

map()实现仅作用于已定义的属性。

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

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

快速创建一个这样的数组:

[0,1,2,3,4,5,6,7,8,9]
但它没有起作用,因为: (见Jonathan Lonowski的回答是) < / p >

解决方案可以是使用Array.prototype.fill ()将数组项填充为任何值(甚至是undefined)

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

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

Update

Another solution could be:

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

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

如果你这样做是为了方便地用值填充数组,由于浏览器支持的原因不能使用填满,并且真的不想做for循环,你也可以使用x = new Array(3).join(".").split(".").map(...,它会给你一个空字符串数组。

我不得不说,这很难看,但至少问题和意图传达得很清楚。

使用ES6,你可以做[...Array(10)].map((a, b) => a),快速和简单!

在ECMAScript第6版规范。

new Array(3)只定义了属性length,而没有定义像{length: 3}这样的索引属性。参见https://www.ecma-international.org/ecma-262/6.0/index.html#sec-array-len步骤9。

[undefined, undefined, undefined]将像{0: undefined, 1: undefined, 2: undefined, length: 3}一样定义索引属性和长度属性。参见https://www.ecma-international.org/ecma-262/6.0/index.html#sec-runtime-semantics-arrayaccumulation 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

如何修复?

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);

ES6解决方案:

[...Array(10)]

但不能在typescript(2.3)上工作

下面是一个简单的实用方法:

简单mapFor

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]

完整的示例

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

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]

倒计时

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

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

既然问题是为什么,这与JS的设计方式有关。

我认为有两个主要原因可以解释这种行为:

  • 性能:给定x = 10000new Array(x),构造函数最好避免从0到10000循环使用undefined值填充数组。

  • 隐式"undefined":给出a = [undefined, undefined]b = new Array(2)a[1]b[1]都将返回undefined,但a[8]b[8]也将返回undefined,即使它们超出了范围。

最后,标记empty x 3是避免设置和显示一长串undefined值的快捷方式,因为它们没有显式声明。

注意:给定数组a = [0]a[9] = 9console.log(a)将返回(10) [0, empty x 8, 9],通过返回显式声明的两个值之间的差值自动填补空白。

由于在其他答案中详细解释的原因,Array(n).map不能工作。然而,在ES2015中Array.from接受一个map函数:

let array1 = Array.from(Array(5), (_, i) => i + 1)
console.log('array1', JSON.stringify(array1)) // 1,2,3,4,5


let array2 = Array.from({length: 5}, (_, i) => (i + 1) * 2)
console.log('array2', JSON.stringify(array2)) // 2,4,6,8,10