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

Posted

技术标签:

【中文标题】带有对象的 Array.prototype.fill() 传递引用而不是新实例【英文标题】:Array.prototype.fill() with object passes reference and not new instance 【发布时间】:2022-01-21 15:52:12 【问题描述】:

我玩了一会儿,试图实例化一个长度为 x 的新数组,其中该数组的所有元素都被初始化为一个值 y

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

如果y 的值不是一个对象,这会很有效。 这意味着如果y 是一个对象,则以下情况为真:

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

有没有什么方法可以说明在使用填充函数时应该为每个元素创建一个新对象?还是我应该将其转换为循环?

【问题讨论】:

另见Declaring array of objects 【参考方案1】:

你可以先fill数组中的任意值(例如undefined),然后你就可以使用map了:

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

【讨论】:

还有Array.from(length:2, u => ()) 不幸的是,这种方式的性能并不是最好的。我在下面的答案中添加了更多解释。 @Slai 非常好的答案,谢谢。我花了一段时间才弄清楚,但这是值得的。【参考方案2】:

已接受的答案很好,适用于 90% 的情况。

但是,如果您正在制作高性能 JS 应用程序,并且如果您使用大/巨大的数组,Array.map(..) 会在两者中创建大的 overload - 内存和处理器使用,因为它创建了一个数组的副本。

我推荐使用经典的 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 []; );

for循环,最好的(最快):

 // 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++] = []);

forEach6 倍时间 慢):

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

[UPDATE 2020-08-27] 下面是 Ilias Karim 提出的另一种方法

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

 a = Array.from( length: ARRAY_SIZE , () => []);

[..Array(..)]5倍!!!慢)

 a = [...Array(ARRAY_SIZE)].map(_=>([]))

Array.push(..),性能第二名(2倍!!!慢)

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

PS。我使用this fiddle 进行测试。

【讨论】:

这不是一个公平和有效的比较,因为您预先初始化了数组并为 for 循环创建了一个 PACKED 数组。您的基准测试将重复的对象初始化时间与预初始化的本机数组迭代进行比较。去掉数组的预填充,或者把元素的类型改成非同质的,这个benchmark就崩溃了。 @user120242 我不明白你的评论?这个问题的意思是:如何用值初始化数组?我只是与接受的答案进行了比较,并发现了更快的方法。如果您对数组初始化有更好的建议,请免费填写与我们分享:)【参考方案3】:

一种高效的解决方案: Array.from( length: 5 , () =&gt; new Object())

【讨论】:

可以写得更短:Array.from(length:5, _=&gt;) 检查 jsperf。自 v80 起,microbenchmarks 的值承受、填充和映射仍然优于 array.from 在 Chrome 中。很可能是因为 PACKED 数组迭代速度更快 我只是在上面的答案中添加了这种初始化方式,显然,就性能而言,这是更糟糕的方式。在我的测试中,与常规 for 循环 相比,这种方式显示出 30 倍 的结果 @morphles 如果你想初始化对象,它应该是_=&gt;()。否则,您只需使用 undefined 填充数组。【参考方案4】:

最短时间:

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

【讨论】:

我也测试了这个,它比旧时尚慢 5 倍 for 循环请参阅上面的答案以进行性能测试。【参考方案5】:

Ilias Karim 的回答非常出色。我刚刚做了以下事情:

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

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

【讨论】:

【参考方案6】:

您也可以通过以下解决方法解决此问题。

var arr = new Array(2).fill();
arr = JSON.parse(JSON.stringify(arr));

【讨论】:

【参考方案7】:

我为此写了一篇博文:http://www.samhenderson.xyz/posts/12

但 TLDR 是,如果您想避免链接多个函数,例如fillmap。并且想避免写循环,那么可以使用:

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

对于数组数组:

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

【讨论】:

正如我在回答中提到的那样,这种方法在性能方面更差......

以上是关于带有对象的 Array.prototype.fill() 传递引用而不是新实例的主要内容,如果未能解决你的问题,请参考以下文章

错误:对象作为 React 子对象无效(找到:带有键 low, high 的对象)

错误:对象作为 React 子项无效(找到:带有键 的对象)。改用数组

带有附加对象的 NSFetchedResultsController

如何找到带有自动释放消息的对象?

TypeError: 'NoneType' 对象不可迭代,使用带有 Selenium/Appium 的页面对象框架

使用 RestKit 序列化带有子对象的对象 [POSTing]