在Javascript中将一个对象数组复制到另一个数组(深度复制)

26

使用slice(0)和concat()在javascript中将一个对象数组复制到另一个数组中是行不通的。

我尝试了以下内容以测试是否使用此方法获得深度复制的预期行为。但是,在我对已复制的数组进行更改后,原始数组也会被修改。

var tags = [];
for(var i=0; i<3; i++) {
    tags.push({
        sortOrder: i,
        type: 'miss'
    })
}
for(var tag in tags) { 
    if(tags[tag].sortOrder == 1) {
        tags[tag].type = 'done'
    }
}
console.dir(tags)

var copy = tags.slice(0)
console.dir(copy)

copy[0].type = 'test'
console.dir(tags)

var another = tags.concat()
another[0].type = 'miss'
console.dir(tags)

如何将一个数组深度复制到另一个数组,使得在复制数组中进行更改时不会修改原始数组。


我不确定你想做什么,但是我建议将你的第二个for循环改为for of循环 for of(var tag of tags) { if(tag.sortOrder == 1) { tag.type == 'done' }} - Edwin Reynoso
根据你的环境,你可以尝试使用Object.create? - Zlatko
@zlatko,你能给一个相同的例子吗? - jsbisht
哦,var newObject = Object.create(oldObject) 应该会给你一个基于 proto 的并解除引用的旧对象克隆。对于数组不太确定,但你可以尝试一下。 该注释中的环境部分是我不知道你是否在 V8 或其他 JS 引擎上,我认为它不是在所有地方都支持的(但有 polyfill)。 - Zlatko
实际上它看起来是: http://kangax.github.io/compat-table/es5/#Object.create - Zlatko
10个回答

78

尝试

var copy = JSON.parse(JSON.stringify(tags));

一年半过去了,仍然有帮助! - Kesem David
7
@Aerious,这个方法可行是因为你将数组/对象序列化为字符串。当你解析该字符串时,会实例化全新的对象,就好像你从某个API接收到了一个负载一样。然而,引用关系不会被保留,所以如果你的数组元素指向其他元素,那么这种方法可能存在危险。 - Sava B.
@SavaB。同意 :) - Aerious
1
请注意,如果数组中的任何对象(标签)是循环结构,即间接引用自身,则此方法将无法正常工作。这种情况会导致“TypeError:Converting circular structure to JSON”错误。 - Wojtek Majerski

14

1
这个不起作用,因为结果是一个对象,而不是一个数组。 - J. Allen

5
如上所述,这里使用.slice(0)将对包含原始类型元素的数组进行克隆是有效的。然而,在您的示例中,tags数组包含匿名对象。因此,在克隆数组中对这些对象进行任何更改都会反映在tags数组中。
@dangh在上面的回复中取消引用了这些元素对象并创建了新的对象。 这里有另一个线程解决了类似的情况。

4
使用ES6中的扩展语法是克隆对象数组的好方法:
const clonedArray = [...oldArray];

MDN


8
这是一个浅拷贝。 - Otto

1
您只需要使用“...”符号即可。
// THE FOLLOWING LINE COPIES all elements of 'tags' INTO 'copy'
var copy = [...tags]

当你有一个数组x时,[...x]会创建一个新的数组并包含x中所有的值。但是要注意,这种表示法在对象上的作用略有不同。它将对象拆分成所有的键和值对。因此,如果你想将对象的所有键值对传递给函数,只需传递function({...obj})即可。

5
他需要进行深拷贝,而不是浅拷贝。 - Chris Johnson
这不会导致深拷贝,新数组中的对象仍将引用旧数组中的对象。 - Nick Briz
哦,好的。抱歉。 - Anant Sinha

1

在编程中,最简单和乐观的方法是使用Underscore/Lodash一行代码实现:

let a = _.map(b, _.clone)


0

使用JSON.parse在JavaScript中深度复制数组的方法:

let orginalArray=
[
 {firstName:"Choton", lastName:"Mohammad", age:26},
 {firstName:"Mohammad", lastName:"Ishaque", age:26}
];

let copyArray = JSON.parse(JSON.stringify(orginalArray));
copyArray[0].age=27;

console.log("copyArray",copyArray);
console.log("orginalArray",orginalArray);


0

我也遇到了同样的问题。我从服务中获取数据并保存到另一个变量中。每当我更新我的数组时,复制的数组也会被更新。旧代码如下:

//$scope.MyData get from service
$scope.MyDataOriginal = $scope.MyData;

所以每当我改变 $scope.MyData 时,$scope.MyDataOriginal 也会随之改变。 我找到了一个解决方案,即使用以下代码的 angular.copy

$scope.MyDataOriginal = angular.copy($scope.MyData);


0

我知道这是一个有点老的帖子,但我很幸运地找到了一种不错的方法来深度复制数组,甚至包含数组和对象,甚至包含数组的对象也被复制了... 我只能看到这段代码的一个问题,就是如果你没有足够的内存,我可以看到它在非常大的数组和对象上会出现问题... 但大部分情况下应该可以工作。我在这里发布这篇文章的原因是它实现了 OP 请求通过值而不是引用复制对象数组... 所以现在有了这个代码(检查来自 SO,主要复制函数是我自己编写的,不是其他人可能已经写过的,我只是不知道):

var isArray = function(a){return (!!a) && (a.constructor===Array);}
var isObject = function(a){return (!!a) && (a.constructor===Object);}
Array.prototype.copy = function(){
    var newvals=[],
        self=this;
    for(var i = 0;i < self.length;i++){
        var e=self[i];
        if(isObject(e)){
            var tmp={},
                oKeys=Object.keys(e);
            for(var x = 0;x < oKeys.length;x++){
                var oks=oKeys[x];
                if(isArray(e[oks])){
                    tmp[oks]=e[oks].copy();
                } else { 
                    tmp[oks]=e[oks];
                }
            }
            newvals.push(tmp);
        } else {
            if(isArray(e)){
                newvals.push(e.copy());
            } else {
                newvals.push(e);
            }
        }
    }
    return newvals;
}

这个函数(Array.prototype.copy)使用递归,当调用对象或数组时,返回所需的值,并反复调用自身。这个过程相当迅速,正好做到了您想要它做的事情——通过值进行数组的深拷贝......在 Chrome 和 IE11 中测试过,在这两个浏览器中都可以使用。

-1

我使用新的ECMAScript 6 Object.assign 方法:

let oldObject = [1,3,5,"test"];
let newObject = Object.assign({}, oldObject)

这个方法的第一个参数是要更新的数组,我们传递一个空对象,因为我们想要得到一个全新的对象,
您也可以添加其他要复制的对象 :
let newObject = Object.assign({}, oldObject, o2, o3, ...)

1
即使使用Object.assign,您仍然会遇到相同的问题。目前在Javascript中存在的每种方法都可以正常处理原始数据类型,但如果您的对象具有将另一个对象作为值的键,则引用将被保留。 - pauldcomanici

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