带有对象的 Array.prototype.fill() 传递引用而不是新实例

Array.prototype.fill() with object passes reference and not new instance

提问人:Koen Morren 提问时间:2/23/2016 最后编辑:MogsdadKoen Morren 更新时间:9/22/2022 访问量:32227

问:

我正在玩弄一点,并试图实例化一个新的长度数组,其中该数组的所有元素都被初始化为一个值:xy

var arr = new Array(x).fill(y);

如果 的值不是对象,则此方法很有效。 这意味着,如果是一个对象,则为真:yy

var arr = new Array(2).fill({});
arr[0] === arr[1]; //is true;
arr[0].test = 'string';
arr[1].test === 'string'; //is also true;

有没有办法说明在使用 fill 函数时应该为每个元素创建一个新对象?还是我应该把它转换成一个循环?

JavaScript 数组

评论

0赞 Bergi 9/20/2021
另请参阅 声明对象数组
0赞 Sebastian Simon 2/5/2022
相关:Array.push() 使推送对象时所有元素都相同

答:

108赞 Oriol 2/23/2016 #1

您可以先使用任何值(例如)创建数组,然后您将能够使用:fillundefinedmap

var arr = new Array(2).fill().map(u => ({}));
var arr = new Array(2).fill().map(Object);

评论

29赞 Slai 2/2/2018
Array.from({length:2}, u => ({}))
0赞 Kostanos 8/27/2020
不幸的是,这种方式的性能并不是最好的。我在下面的答案中添加了更多解释。
0赞 Cooper 3/30/2021
@Slai非常好的答案,谢谢你。我花了一段时间才弄清楚,但这是值得的。
0赞 ggorlen 9/22/2022
这里不需要。这是一个良好的开端,但目前其他答案更全面。 目前只是打字较多,速度较慢。newArray.from
47赞 Kostanos 4/18/2017 #2

公认的答案是好的,并且在 90% 的情况下都有效。

但是,如果你正在制作高性能的 JS 应用程序,并且如果你使用大/巨大的数组,Array.map(..) 会在内存和处理器使用方面产生很大的过载,因为它会创建一个数组的副本。

我建议使用经典的for循环:

    a = new Array(ARRAY_SIZE);
    for (var i = 0; i < ARRAY_SIZE; i++) {
        a[i] = [];
    }
    // or it's one line alternative
    for (var i = 0, a = []; i < ARRAY_SIZE; a[i++] = []);

我测试了六个替代方案,得到了这个:

  • Array.map(),如上所述(慢 11 倍!!):

     a = new Array(ARRAY_SIZE).fill().map(u => { return []; });
    
  • 对于循环,最好的(最快):

     // Standard multi-line way
     a = new Array(ARRAY_SIZE);
     for (var i = 0; i < ARRAY_SIZE; i++) {
         a[i] = [];
     }
    
     // One line syntax
     for (var i = 0, a = []; i < ARRAY_SIZE; a[i++] = []);
    
  • forEach慢 6 倍):

     a = new Array(ARRAY_SIZE).fill();
     a.forEach((val, i) => {
         a[i] = [];
     })
    

[更新 2020-08-27]下面伊利亚斯·卡里姆(Ilias Karim)提出的另一种方式

  • Array.from慢 30 倍!! - 尽管语法:(最好,但性能显然更差

     a = Array.from({ length: ARRAY_SIZE }, () => []);
    
  • [..数组(..)]5 倍!!慢)

     a = [...Array(ARRAY_SIZE)].map(_=>([]))
    
  • Array.push(..),性能第二(2 倍!!慢)

     let a = [], total = ARRAY_SIZE;
     while(total--) a.push([]);
    

我用这把小提琴进行测试。

评论

4赞 user120242 6/21/2020
这不是一个公平或有效的比较,因为您预先初始化了数组并为 for 循环创建了一个 PACKED 数组。您的基准测试正在将重复的对象初始化时间与预先初始化的本机数组迭代进行比较。去掉数组的预填充,或者将元素的类型更改为非均匀的,这个基准测试就会分崩离析。
3赞 Kostanos 8/27/2020
@user120242我不明白你的评论?这个问题的含义是关于:如何用值初始化数组?我只是与公认的答案进行了比较,并发现了更快的方法。如果您对数组初始化有更好的建议,请填写免费与我们分享:)
21赞 Ilias Karim 5/10/2018 #3

一种高性能解决方案:Array.from({ length: 5 }, () => new Object())

评论

1赞 morphles 4/21/2020
可以写得更短:Array.from({length:5}, _=>{})
0赞 user120242 6/21/2020
检查 jsperf。微基准测试值 withwith, fill 和 map 在 Chrome 中的表现仍然优于 array.from 从 v80 开始。很可能是因为 PACKED 数组迭代速度更快
2赞 Kostanos 8/27/2020
我刚刚在上面的答案中添加了这种初始化方式,显然,就性能而言,这是最差的方式。在我的测试中,与常规的 for 循环相比,这种方式的结果慢了 30 倍
3赞 VLAZ 11/26/2020
@morphles如果要初始化对象,则应如此。否则,您只需用 ._=>({})undefined
0赞 ggorlen 9/22/2022
Array.from({ length: 5 }, Object)也可以,但更短更快。所以很多答案只是在没有任何证据的情况下说“这是性能的”。请凭经验展示您的基准。[...Array(5)].map(Object)
2赞 Sideways S 8/25/2018 #4

伊利亚斯·卡里姆(Ilias Karim)的回答非常出色。我只是做了以下事情:

a = Array.from({length:l}, () => new Array(c).fill(prefix));

创建一个指定大小的预填充 2D 数组,l x c,填充前缀。现在,我的代码可以填充 2D 矩阵中需要非前缀值的插槽。

评论

0赞 ggorlen 9/22/2022
这里不需要,并且必须是原始的(除非你想要锯齿)。newprefix
0赞 Sideways S 9/23/2022
哇,四年后......你是对的,但你可能会注意到,原来的问题和许多后续的答案都使用了“new”,所以我当时只是在模仿它们。否则,感谢您的更正。
5赞 Rick 11/3/2018 #5

最短的可能:

let node =  [...Array(2)].map(_=>({}))
console.log(node)

评论

0赞 Kostanos 8/27/2020
我也测试了这个,它比老式的 for 循环慢 5 倍,请参阅我上面的性能测试答案。
0赞 Sam Henderson 7/1/2021 #6

我写了一篇关于这个的博文:http://www.samhenderson.xyz/posts/12

但 TLDR 是,如果您想避免链接多个功能,例如 , .并且想要避免编写循环,那么你可以使用:fillmap

const array = Array.from({ length: 2 },()=>({}))

对于数组的数组:

const array = Array.from({ length: 2 },()=>([]))

评论

0赞 Kostanos 11/19/2021
正如我在回答中提到的,这种方法在性能方面更差......
0赞 ggorlen 9/22/2022
Array.from({length: n}, () => {})或者字符也比它多,似乎值得忽略。Array.from(Array(n), () => {})[...Array(n)].map(() => {})
0赞 ggorlen 9/21/2022 #7

为了补充解释别名问题以及如何解决它的答案,这里有一个方便的函数,可用于为调用方创建语法更简洁的数组:

const array = (length, fill) =>
  [...Array(length)].map((_, i) =>
    typeof fill === "function" ? fill(i) : fill
  );


// usage:
const a = array(3, i => array(3, j => [i, j]));
a[0][0][0] = -42;
console.log(a);

请注意,您仍然需要对非基元值使用回调函数。这实际上是一项功能,因为它公开了索引,并允许您提供任意逻辑来填充元素。如果您担心意外地将非基元、非函数对象作为填充值传递,则可能会引发错误。

如果你真的希望能够直接传递一个对象,并在后台复制它,这里有一个几乎禁止混叠的调整:

const array = (length, fill) =>
  [...Array(length)].map((x, i) =>
    typeof fill === "function" ? fill(i) :
    typeof fill === "object" ? _.cloneDeep(fill) : fill
  );


// usage:
const a = array(2, array(2, {foo: 3}));
a[0][0].foo = 42;
console.log(a);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>

一般来说,我建议几乎完全避免使用传播语法,因为很容易忘记别名行为并最终出现令人沮丧的错误。.fill()[...Array()]

如果速度很重要,请使用传统循环:for

const array = (length, fill) => {
  const a = [];

  for (let i = 0; i < length; i++) {
    a[i] = typeof fill === "function" ? fill(i) : fill;
  }

  return a;
};

// usage:
const a = array(2, () => array(2, Object));
a[0][0].foo = 42;
console.log(a);