为什么使用`new Array(count)`创建的数组似乎无法使用`map`方法?

313

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


我没有从数组字面量符号中获得您所看到的相同结果。 我仍然得到undefined而不是0。 只有当我设置像 var y = x.map(function(){return 0; }); 这样的东西时,我才会得到0的结果,并且我在使用 new Array() 方法和数组字面量时都会得到这个结果。 我在Firefox 4和Chrome中进行了测试。 - RussellUresti
同样在Chrome中出现问题,这可能是由于语言定义的原因,尽管这没有任何意义,所以我真的希望不是这个原因。 - Hashbrown
当你使用 new Array(4) 时,结果不是一个有 4 个 "undefined" 的数组,而是一个不同的结果 - 你得到了 "(4) [empty × 4]"。 - yehonatan yehezkel
14个回答

194

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

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

快速创建如下形式的数组:

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

但是它没有起作用,因为:Jonathan Lonowski的回答

解决方案可能是使用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));

更新

另一种解决方案可能是:

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

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


40
值得注意的是,在.fill()方法中不需要声明undefined,可以将代码简化为let arr = new Array(10).fill().map((val,idx) => idx); - Yann Eves
1
同样地,您可以使用 Array.from(Array(10)) - user10275798
1
我会推荐@eden-landau的答案,因为这是一种更清晰的初始化数组的方式。 - Sebastien H.
答案不正确。 - Piliponful
@TrevorKarjanis 谢谢你的提示,但感觉不相关。我在这里需要一个对象吗? - cstuncsik
显示剩余4条评论

153

看起来第一个例子

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 函数运行在索引 / 属性列表上,而不是集合长度上,因此如果没有创建索引 / 属性,它将不会运行。


107
根据MDC(重点标注为我的)的解释:“map方法对数组中每个元素按顺序调用提供的回调函数,并从结果构造一个新数组。**callback仅针对已分配值的数组索引进行调用;它不会调用已删除或从未分配值的索引。**”在这种情况下,变量x的值没有被显式地赋值,而变量y的值被赋了值,即使它是undefined值。 - Martijn
2
那么,是 JavaScript 的问题导致无法检查它是否为未定义指针,还是指向未定义的指针?我的意思是 (new Array(1))[0] === [undefined][0] - Trevor Norris
8
@TrevNorris,您可以通过使用hasOwnProperty轻松测试,除非hasOwnProperty本身存在漏洞:(new Array(1)).hasOwnProperty(0) === false[undefined].hasOwnProperty(0) === true。实际上,您可以用相同的方式使用in0 in [undefined] === true0 in new Array(0) === false - squid314
3
在 JavaScript 中谈论“未定义指针”会使问题变得混乱。你要找的术语是“省略符”(elisions)(请参见https://people.mozilla.org/~jorendorff/es6-draft.html#sec-array-initializer)。`x = new Array(3);相当于x = [,,,];,而不是 x = [undefined, undefined, undefined]`。 - Matt Kantor
我从未声称这两者相等,相反,它们是不相等的。我强调,在第一种情况下,数组具有大小但没有内容,映射函数无法运行,而在第二种情况下,即使对象未定义,也存在对象,并且只要您不尝试访问对象,映射可以运行。 OP被混淆了,因为这两个在firebug中呈现相同的输出,但在map函数中的行为不同。 - David Mårtensson
显示剩余3条评论

118

使用ES6,您可以快速轻松地执行[...Array(10)].map((a, b) => a)操作!


11
在ES6之前,您可以使用new Array(10).fill()。这与[...Array(10)]获得相同的结果。 - Molomby
2
使用大数组时,展开语法会产生问题,最好避免使用。 - user10275798
或者 [...Array(10).keys()] - Chungzuwalla
4
我知道它是如何工作的,但请添加一些解释,以便那些不了解的人。 - vsync

36

ES6 的解决方案:

[...Array(10)]

虽然在 TypeScript (2.3) 上无法工作


7
Array(10).fill("").map( ... 是我在使用 TypeScript 2.9 时得到的有效方法。 - ibex

34
map的MDC页面上可以看到:

[...] 只有已赋值的数组索引才会调用callback函数; [...]

[undefined]实际上在该索引处设置setter,以便map可迭代,而new Array(1)仅将该索引初始化为默认值undefined,因此map将跳过它。
我认为这对于所有迭代方法都是一样的。

23

出于其他答案中充分解释的原因,Array(n).map 不起作用。然而,在 ES2015 中,Array.from 接受一个映射函数:

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


1
这对我来说是最干净的答案。 - Sebastien H.

23

这两个数组不同。不同之处在于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 ,则返回的结果与先前相同。


10

在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-arrayaccumulationElementList步骤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);

7

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

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

实际上发生的是,new Array() 返回一个长度为3但没有值的空数组。因此,当您在技术上为空的数组上运行x.map时,没有任何内容可以设置。Firefox只是使用undefined填充这些空槽,即使它没有值。我认为这不是明确的错误,只是表示正在发生的事情的一种贫乏方式。我想Chrome的做法更加“正确”,因为它显示了数组中实际上没有任何内容。

6

这不是一个bug。这就是Array构造函数的定义方式。

来自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 - 1length 的值隐式地对所有元素执行值分配。因此,在使用 Array 构造函数和数字参数新构建的数组上调用 .map() 函数不会做任何事情(不会调用回调)。

它被定义为破碎的吗?它被设计成产生一个始终为“未定义”的三个元素数组吗? - Lightness Races in Orbit
是的,除了“forever”部分之外,这是正确的。您随后可以为元素分配值。 - Pointy
3
所以你应该使用 x = [] 而不是 x = new Array() - gen_Eric

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接