为什么“map”方法显然不适用于通过“new Array(count)”创建的数组?

Why does the `map` method apparently not work on arrays created via `new Array(count)`?

提问人:rampion 提问时间:3/31/2011 最后编辑:user3840170rampion 更新时间:4/14/2023 访问量:102441

问:

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

javascript 数组 map-function

评论

0赞 RussellUresti 3/31/2011
我没有得到你从数组文字表示法中看到的相同结果。我仍然得到 undefined 而不是 0。只有当我设置类似 的东西时,我才会得到 0 结果,并且我为新的 Array() 方法和数组文字都得到了这个结果。我在 Firefox 4 和 Chrome 中进行了测试。var y = x.map(function(){return 0; });
0赞 Hashbrown 1/29/2020
在 Chrome 中也被破坏了,这可能在语言中定义,尽管它没有意义,所以我真的希望它不是
0赞 yehonatan yehezkel 5/31/2022
当你使用 new Array(4) 时,resul tis not 数组有 4 个 “undefined”,你得到不同的结果 - 你得到 “(4) [empty × 4]”

答:

153赞 David Mårtensson 3/31/2011 #1

看来第一个例子

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赞 Martijn 3/31/2011
From MDC (强调我的): “按顺序为数组中的每个元素调用一次提供的回调函数,然后根据结果构造一个新数组。callback 仅对具有赋值的数组索引调用;对于已删除或从未赋值的索引,不会调用它。在本例中,的值没有显式赋值,而 的值是赋值的,即使它是值 。mapxyundefined
2赞 Trevor Norris 9/8/2012
那么,是 JavaScript 失败,无法检查它是未定义的指针,还是指向未定义的指针?我的意思是。(new Array(1))[0] === [undefined][0]
0赞 David Mårtensson 9/18/2012
好吧,undefined 数组不同于指向 undefined 对象的指针数组。undefined 数组类似于 null 值数组 [null, null, null],而指向 undefined 的指针数组类似于 [343423, 343424, 343425] 指向 null 和 null 和 null。第二种解决方案具有指向内存地址的真实指针,而第一种解决方案不指向任何位置。如果这是 JS 的失败,这可能是一个讨论的问题,但这里不是;)
8赞 squid314 3/20/2015
@TrevNorris,您可以轻松地使用它来测试它,除非它本身有一个错误: 和 .事实上,你可以用 和 做完全相同的事情。hasOwnPropertyhasOwnProperty(new Array(1)).hasOwnProperty(0) === false[undefined].hasOwnProperty(0) === truein0 in [undefined] === true0 in new Array(0) === false
3赞 Matt Kantor 4/14/2015
在 JavaScript 中谈论“未定义的指针”混淆了这个问题。你要找的术语是“省略”。 等价于 ,而不是 。x = new Array(3);x = [,,,];x = [undefined, undefined, undefined]
6赞 Pointy 3/31/2011 #2

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

来自 MDC:

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

var billingMethod = new Array(5);

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

该方法仅在数组的迭代元素中包含已显式分配了值的元素。即使是显式赋值也会导致值被视为有资格包含在迭代中。这看起来很奇怪,但它本质上是对象上的显式属性和缺失属性之间的区别:.map()undefinedundefined

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

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

评论

0赞 Lightness Races in Orbit 3/31/2011
它被定义为被打破?它旨在产生一个永远存在的三个元素的数组?undefined
0赞 Pointy 3/31/2011
是的,没错,除了“永远”部分。随后,您可以为元素赋值。
3赞 gen_Eric 3/31/2011
这就是为什么你应该使用而不是x = []x = new Array()
34赞 Jonathan Lonowski 3/31/2011 #3

地图的 MDC 页面:

[...] 仅对具有赋值的数组索引调用;[...]callback

[undefined]实际上在索引上应用 setter,以便迭代,而仅使用默认值 so 初始化索引会跳过它。mapnew Array(1)undefinedmap

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

7赞 helloandre 3/31/2011 #4

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

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

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

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

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

23赞 Tim Down 3/31/2011 #5

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

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

这源于一个有点令人困惑的事实,即如果你试图获取 JavaScript 中任何本机对象的不存在属性的值,它会返回(而不是抛出错误,就像你尝试引用不存在的变量时发生的那样),这与你得到的相同,如果该属性之前被显式设置为 .undefinedundefined

4赞 Vezquex 10/29/2013 #6

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

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

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

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

194赞 cstuncsik 1/29/2016 #7

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

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

要快速创建如下所示的数组,请执行以下操作:

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

但它没有奏效,因为: (参见乔纳森·洛诺夫斯基的回答)

解决方案可能是使用 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赞 Yann Eves 2/11/2016
值得注意的是,您不需要在方法中声明,将代码简化为非常轻微undefined.fill()let arr = new Array(10).fill().map((val,idx) => idx);
1赞 7/15/2019
同样,您可以使用Array.from(Array(10))
1赞 Sebastien H. 8/6/2021
我会推荐 @eden-landau 的答案,因为它是一种更简洁的初始化数组的方法
0赞 Piliponful 2/22/2022
答案是不正确的
0赞 Trevor Karjanis 5/20/2022
请记住在映射到对象时使用 return。:XArray.apply(null, Array(10)).map(() => { return {}; });
3赞 Alex 3/21/2016 #8

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

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

118赞 Manuel Beaudru 10/6/2016 #9

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

评论

11赞 Molomby 8/1/2017
在 ES6 之前,您可以使用 .结果与new Array(10).fill()[...Array(10)]
2赞 7/15/2019
对于大型数组,扩展语法会产生问题,因此最好避免
0赞 Chungzuwalla 5/19/2020
[...Array(10).keys()]
4赞 vsync 10/28/2020
我知道它是如何工作的,但请为那些不这样做的人添加一些解释
10赞 wenshin 4/18/2017 #10

在 ECMAScript 第 6 版规范中。

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

[undefined, undefined, undefined]将定义索引属性和长度属性,如 .请参阅 https://www.ecma-international.org/ecma-262/6.0/index.html#sec-runtime-semantics-arrayaccumulation 步骤 5。{0: undefined, 1: undefined, 2: undefined, length: 3}ElementList

Array 的方法 , , , , 将通过内部方法检查 index 属性,因此不会调用回调。mapeverysomeforEachslicereducereduceRightfilterHasPropertynew 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);
35赞 Serge Intern 5/5/2017 #11

ES6 解决方案:

[...Array(10)]

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

评论

7赞 ibex 8/2/2018
Array(10).fill("").map( ...是 Typescript 2.9 对我有用的东西
0赞 DJDaveMark 10/10/2018 #12

下面是一个简单的实用方法作为解决方法:

简单地图用于

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]
1赞 BPS Julien 9/15/2019 #13

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

我能想到的两个主要原因来解释这种行为:

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

  • 隐式“未定义”:Give 和 ,并且都将返回 ,但 和 即使超出范围,也会返回。a = [undefined, undefined]b = new Array(2)a[1]b[1]undefineda[8]b[8]undefined

归根结底,表示法是一种快捷方式,可以避免设置和显示一长串值,这些值无论如何都是,因为它们没有显式声明。empty x 3undefinedundefined

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

23赞 Eden Landau 3/26/2020 #14

由于在其他答案中彻底解释的原因,不起作用。但是,在 ES2015 中,Array.from 接受映射函数:Array(n).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

评论

1赞 Sebastien H. 8/6/2021
这对我来说是最干净的答案。