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


209

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

当我启动萤火虫时:

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)


我没有从数组文字符号中看到相同的结果。我仍然得到undefined而不是0。如果设置为var y = x.map(function(){return 0; });,则只能得到0结果,并且对于新的Array()方法和数组文字都可以得到该结果。我在Firefox 4和Chrome中进行了测试。
罗素·乌雷斯蒂2011年

也用Chrome破坏了,这可能是用语言定义的,尽管这没有任何意义,所以我真的希望它不是
Hashbrown

Answers:


125

看来第一个例子

x = new Array(3);

用未定义的指针创建一个数组。

第二个对象创建一个带有指向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可以运行。


86
MDC(重点是我的):“ map对数组中的每个元素依次调用提供的回调函数,并从结果中构造一个新数组。callback仅对已分配值的数组索引调用;不调用对于已删除或从未分配值的索引。” 在这种情况下,x的值未明确分配值,而y的值已分配,即使它是value undefined
Martijn

2
难道是JavaScript失败了,无法检查它是未定义的指针还是指向未定义的指针?我是说(new Array(1))[0] === [undefined][0]
Trevor Norris 2012年

好吧,未定义的数组不同于指向未定义的对象的指针的数组。未定义的数组将类似于空值数组[null,null,null],而指向undefined的指针数组将类似于[343423、343424、343425]插入null,null和null。第二种解决方案具有指向内存地址的实际指针,而第一种则没有指向任何地方。如果这不是JS的失败,可能是讨论的问题,但不是这里;)
DavidMårtensson12年

4
@TrevNorris,您可以轻松地使用进行测试,hasOwnProperty除非hasOwnProperty它本身具有错误:(new Array(1)).hasOwnProperty(0) === false[undefined].hasOwnProperty(0) === true。实际上,您可以使用in0 in [undefined] === true和进行完全相同的操作0 in new Array(0) === false
squid314

3
在JavaScript中谈论“未定义的指针”会使这个问题感到困惑。您要查找的术语是“ elisions”x = new Array(3);等价于x = [,,,];,不等于x = [undefined, undefined, undefined]
马特·坎托

118

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

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

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

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

但这不起作用,原因是:(请参阅上述乔纳森·洛诺夫斯基的答案)

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

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

更新资料

另一个解决方案可能是:

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

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


28
值得一提的,你并不需要状态undefined.fill()方法,非常轻微地简化了代码let arr = new Array(10).fill().map((val,idx) => idx);
晏前夕

同样,您可以使用Array.from(Array(10))


25

ES6解决方案:

[...Array(10)]

不过不适用于打字稿(2.3)


6
Array(10).fill("").map( ...是使用Typescript 2.9对我有用的东西
ibex

19

数组是不同的。不同之处在于,new Array(3)创建的数组的长度为三个,但没有属性,而[undefined, undefined, undefined]创建的数组的长度为三个,则三个属性分别为“ 0”,“ 1”和“ 2”,每个属性的值为undefined。您可以使用in运算符查看差异:

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

这源于一个令人困惑的事实,即如果您尝试获取JavaScript中任何本机对象的不存在属性的值,它都会返回undefined(而不是抛出错误,就像您尝试引用不存在的变量时发生的那样) ),与先前将该属性明确设置为时所获得的内容相同undefined


17

在MDC页面上map

[...] callback仅对已分配值的数组索引调用;[...]

[undefined]实际上将setter应用于索引,以便map进行迭代,而new Array(1)只需使用默认值undefinedso 初始化索引即可map跳过它。

我相信所有迭代方法都一样


8

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

非常好的解释。
Dheeraj Rao

7

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

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

因此,实际发生的情况是new Array()返回了一个长度为3但没有值的空数组。因此,当您x.map技术上空的数组上运行时,没有任何设置。

Firefox undefined即使没有值,也只是“填充”这些空白位置。

我认为这不是明确的错误,只是表示正在发生的事情的一种不好的方式。我认为Chrome浏览器的“更正确”,因为它表明数组中实际上没有任何内容。


4

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

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

[undefined, undefined, undefined]创建编号的属性:
{0: undefined, 1: undefined, 2: undefined, length: 3}

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


3

不是错误。这就是定义数组构造函数的方式。

从MDC:

当您使用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确实对从零到的所有元素执行值分配length - 1.map()因此,在使用Array构造函数和数值参数重新构造的数组上调用该函数时,它不会做任何事情(不会调用回调函数)。


它被定义为坏了吗?它旨在产生三个元素的数组,这些元素将永远永远存在undefined吗?
Lightness Races in Orbit

是的,那是正确的,除了“永远”部分。随后,您可以为元素分配值。
Pointy

3
这就是为什么您应该使用x = []而不是x = new Array()
Rocket Hazmat

3

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

我不得不说很丑陋,但是至少可以清楚地传达出问题和意图。


1

因为问题是为什么,所以这与JS的设计有关。

我可以想到2个主要原因来解释此行为:

  • 性能:给定x = 10000new Array(x)构造函数应避免从0到10000循环以用undefined值填充数组,这是明智的。

  • 隐“未定义”:给予a = [undefined, undefined]b = new Array(2)a[1]b[1]都将返回undefined,但a[8]b[8]也将返回undefined即使他们是超出范围。

最终,该符号empty x 3是避免设置和显示一长串undefined值的捷径,这些值undefined无论如何都是未明确声明的。

注:鉴于阵列a = [0]a[9] = 9console.log(a)将返回(10) [0, empty x 8, 9],通过返回两个值之间的差异自动填充间隙显式声明。


1

由于其他答案中已详细解释的原因,因此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


0

这是一个简单的实用程序方法作为解决方法:

简单的地图

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]
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.