…每个对象在同一个数组中也有对其他对象的引用?

当我第一次想到这个问题的时候,我就想到了

var clonedNodesArray = nodesArray.clone()

并搜索如何在JavaScript中克隆对象的信息。我确实在Stack Overflow上找到了一个问题(同样由@JohnResig回答),他指出用jQuery你可以做到

var clonedNodesArray = jQuery.extend({}, nodesArray);

克隆对象。虽然我尝试了这个,但这只复制了数组中对象的引用。如果我

nodesArray[0].value = "red"
clonedNodesArray[0].value = "green"

nodesArray[0]和clonedNodesArray[0]的值将显示为“绿色”。然后我尝试了

var clonedNodesArray = jQuery.extend(true, {}, nodesArray);

它深度复制了一个对象,但我分别从Firebug和Opera Dragonfly得到了“太多递归”和“控制堆栈溢出”的消息。

你会怎么做?这是不应该做的事情吗?在JavaScript中是否有可重用的方法来做到这一点?


当前回答

使用structuredClone创建深度拷贝

在JavaScript中深度复制数组的现代方法是使用structuredClone:

array2 = structuredClone(array1);

然而,这个功能相对较新(Chrome 98, Firefox 94),目前只有大约85%的用户可以使用,所以它还没有准备好投入生产。

作为替代方案,您可以使用下面支持良好的基于json的解决方案之一。

使用JSON.parse创建深度副本

一个通用的解决方案,即在对象数组中考虑所有可能的对象可能是不可能的。 也就是说,如果数组包含具有json序列化内容的对象(没有函数,没有Number。POSITIVE_INFINITY等)一个以性能为代价避免循环的简单方法就是这种纯香草式的单行解决方案。

let clonedArray = JSON.parse(JSON.stringify(nodesArray))

总结一下下面的评论,这种方法的主要优点是它还克隆数组的内容,而不仅仅是数组本身。主要的缺点是它只能处理json序列化的内容,而且它的性能比spread方法慢30倍。

如果数组中有浅对象,并且IE6是可以接受的,那么更好的方法是将展开操作符与.map数组操作符结合使用。对于两层深的情况(如下面附录中的数组):

clonedArray = nodesArray.map(a => {return {...a}})

原因有两个:1)它的速度要快得多(参见下面的基准比较),而且它还允许数组中的任何有效对象。

*附件: 性能量化是基于克隆这个对象数组一百万次:

 [{url: 'https://github.com/bobziroll/scrimba-react-bootcamp-images/blob/master/pic1.jpg?raw=true', id: '1', isFavorite: false}, {url: 'https://github.com/bobziroll/scrimba-react-bootcamp-images/blob/master/pic2.jpg?raw=true', id: '2', isFavorite: false},{url: 'https://github.com/bobziroll/scrimba-react-bootcamp-images/blob/master/pic3.jpg?raw=true', id: '3', isFavorite: false},{url: 'https://github.com/bobziroll/scrimba-react-bootcamp-images/blob/master/pic4.jpg?raw=true', id: '4', isFavorite: false},{url: 'https://github.com/bobziroll/scrimba-react-bootcamp-images/blob/master/pic5.jpg?raw=true', id: '5', isFavorite: true},{url: 'https://github.com/bobziroll/scrimba-react-bootcamp-images/blob/master/pic6.jpg?raw=true', id: '6', isFavorite: false},{url: 'https://github.com/bobziroll/scrimba-react-bootcamp-images/blob/master/pic7.jpg?raw=true', id: '7', isFavorite: false},{url: 'https://github.com/bobziroll/scrimba-react-bootcamp-images/blob/master/pic8.jpg?raw=true', id: '8', isFavorite: false},{url: 'https://github.com/bobziroll/scrimba-react-bootcamp-images/blob/master/pic9.jpg?raw=true', id: '9', isFavorite: false},{url: 'https://github.com/bobziroll/scrimba-react-bootcamp-images/blob/master/pic10.jpg?raw=true', id: '10', isFavorite: false},{url: 'https://github.com/bobziroll/scrimba-react-bootcamp-images/blob/master/pic11.jpg?raw=true', id: '11', isFavorite: false},{url: 'https://github.com/bobziroll/scrimba-react-bootcamp-images/blob/master/pic12.jpg?raw=true', id: '12', isFavorite: false},{url: 'https://github.com/bobziroll/scrimba-react-bootcamp-images/blob/master/pic13.jpg?raw=true', id: '13', isFavorite: false},{url: 'https://github.com/bobziroll/scrimba-react-bootcamp-images/blob/master/pic14.jpg?raw=true', id: '14', isFavorite: false},{url: 'https://github.com/bobziroll/scrimba-react-bootcamp-images/blob/master/pic15.jpg?raw=true', id: '15', isFavorite: false},{url: 'https://github.com/bobziroll/scrimba-react-bootcamp-images/blob/master/pic16.jpg?raw=true', id: '16', isFavorite: false},{url: 'https://github.com/bobziroll/scrimba-react-bootcamp-images/blob/master/pic17.jpg?raw=true', id: '17', isFavorite: false},{url: 'https://github.com/bobziroll/scrimba-react-bootcamp-images/blob/master/pic18.jpg?raw=true', id: '18', isFavorite: false},{url: 'https://github.com/bobziroll/scrimba-react-bootcamp-images/blob/master/pic19.jpg?raw=true', id: '19', isFavorite: false},{url: 'https://github.com/bobziroll/scrimba-react-bootcamp-images/blob/master/pic20.jpg?raw=true', id: '20', isFavorite: false},{url: 'https://github.com/bobziroll/scrimba-react-bootcamp-images/blob/master/pic21.jpg?raw=true', id: '21', isFavorite: false},{url: 'https://github.com/bobziroll/scrimba-react-bootcamp-images/blob/master/pic22.jpg?raw=true', id: '22', isFavorite: false},{url: 'https://github.com/bobziroll/scrimba-react-bootcamp-images/blob/master/pic23.jpg?raw=true', id: '23', isFavorite: false}]

使用:

let clonedArray = JSON.parse(JSON.stringify(nodesArray))

or:

clonedArray = nodesArray.map(a => {return {...a}})

map/spread方法每次传递花费0.000466毫秒,JSON。解析和JSON。Stringify 0.014771毫秒每传递

其他回答

数组中。Slice可以用来复制一个数组或数组的一部分…

这将与字符串和数字工作..-改变一个数组中的字符串不会影响另一个数组-但对象仍然只是通过引用复制,所以改变一个数组中引用的对象会影响另一个数组。

下面是一个JavaScript撤销管理器的示例,它可能对此很有用:http://www.ridgway.co.za/archive/2007/11/07/simple-javascript-undo-manager-for-dtos.aspx

var game_popularity = [
     { game: "fruit ninja", popularity: 78 },
     { game: "road runner", popularity: 20 },
     { game: "maze runner", popularity: 40 },
     { game: "ludo", popularity: 75 },
     { game: "temple runner", popularity: 86 }
];
console.log("sorted original array before clonning");
game_popularity.sort((a, b) => a.popularity < b.popularity);
console.log(game_popularity);


console.log("clone using object assign");
const cl2 = game_popularity.map(a => Object.assign({}, a));
cl2[1].game = "clash of titan";
cl2.push({ game: "logan", popularity: 57 });
console.log(cl2);


// Adding new array element doesnt reflect in original array
console.log("clone using concat");
var ph = []
var cl = ph.concat(game_popularity);

// Copied by reference ?
cl[0].game = "rise of civilization";

game_popularity[0].game = 'ping me';
cl.push({ game: "angry bird", popularity: 67 });
console.log(cl);

console.log("clone using ellipses");
var cl3 = [...game_popularity];
cl3.push({ game: "blue whale", popularity: 67 });
cl3[2].game = "harry potter";
console.log(cl3);

console.log("clone using json.parse");
var cl4 = JSON.parse(JSON.stringify(game_popularity));
cl4.push({ game: "home alone", popularity: 87 });
cl4[3].game ="lockhead martin";
console.log(cl4);

console.log("clone using Object.create");
var cl5 = Array.from(Object.create(game_popularity));
cl5.push({ game: "fish ville", popularity: 87 });
cl5[3].game ="veto power";
console.log(cl5);


// Array function
console.log("sorted original array after clonning");
game_popularity.sort((a, b) => a.popularity < b.popularity);
console.log(game_popularity);


console.log("Object.assign deep clone object array");
console.log("json.parse deep clone object array");
console.log("concat does not deep clone object array");
console.log("ellipses does not deep clone object array");
console.log("Object.create does not deep clone object array");

输出

sorted original array before clonning
[ { game: 'temple runner', popularity: 86 },
{ game: 'fruit ninja', popularity: 78 },
{ game: 'ludo', popularity: 75 },
{ game: 'maze runner', popularity: 40 },
{ game: 'road runner', popularity: 20 } ]
clone using object assign
[ { game: 'temple runner', popularity: 86 },
{ game: 'clash of titan', popularity: 78 },
{ game: 'ludo', popularity: 75 },
{ game: 'maze runner', popularity: 40 },
{ game: 'road runner', popularity: 20 },
{ game: 'logan', popularity: 57 } ]
clone using concat
[ { game: 'ping me', popularity: 86 },
{ game: 'fruit ninja', popularity: 78 },
{ game: 'ludo', popularity: 75 },
{ game: 'maze runner', popularity: 40 },
{ game: 'road runner', popularity: 20 },
{ game: 'angry bird', popularity: 67 } ]
clone using ellipses
[ { game: 'ping me', popularity: 86 },
{ game: 'fruit ninja', popularity: 78 },
{ game: 'harry potter', popularity: 75 },
{ game: 'maze runner', popularity: 40 },
{ game: 'road runner', popularity: 20 },
{ game: 'blue whale', popularity: 67 } ]
clone using json.parse
[ { game: 'ping me', popularity: 86 },
{ game: 'fruit ninja', popularity: 78 },
{ game: 'harry potter', popularity: 75 },
{ game: 'lockhead martin', popularity: 40 },
{ game: 'road runner', popularity: 20 },
{ game: 'home alone', popularity: 87 } ]
clone using Object.create
[ { game: 'ping me', popularity: 86 },
{ game: 'fruit ninja', popularity: 78 },
{ game: 'harry potter', popularity: 75 },
{ game: 'veto power', popularity: 40 },
{ game: 'road runner', popularity: 20 },
{ game: 'fish ville', popularity: 87 } ]
sorted original array after clonning
[ { game: 'ping me', popularity: 86 },
{ game: 'fruit ninja', popularity: 78 },
{ game: 'harry potter', popularity: 75 },
{ game: 'veto power', popularity: 40 },
{ game: 'road runner', popularity: 20 } ]

Object.assign deep clone object array
json.parse deep clone object array
concat does not deep clone object array
ellipses does not deep clone object array
Object.create does not deep clone object array
$.evalJSON($.toJSON(origArray));
function deepCloneArray(array) {
    return Array.from(Object.create(array));
}

jQuery扩展工作正常。你只需要指定你克隆的是一个数组而不是一个对象(注意extend方法的参数是[]而不是{}):

var clonedNodesArray = jQuery.extend([], nodesArray);