在JavaScript中克隆对象

Clone object in JavaScript

本问题已经有最佳答案,请猛点这里访问。

考虑下面的代码或检查这个小提琴。

1
2
3
4
5
6
7
8
9
10
11
var obj = {
    name:"abc",
    age: 20
}

var objTwo;

console.log(obj.age);
objTwo = obj;
objTwo.age = 10;
console.log(obj.age);

我创建了一个名为obj的对象,它有两个属性。现在我将obj分配给另一个名为objtwo的对象。现在我更新了objtwo中的一个属性。同样的变化也反映在obj上。如何在不创建引用的情况下将值从一个对象分配给另一个对象?


这将不通过引用分配

1
2
3
4
5
6
7
8
9
10
11
12
    var obj =
    {
        name: 'abc',
        age: '30'
    };

    var objTwo = {};

    for( var i in obj )
    {
        objTwo[i] = obj[i];
    }

查看小提琴


如果只需要克隆简单对象,只需执行

JSON.parse (JSON.stringify (obj))

就够了。

但这显然不适用于所有情况,因为JSON.stringify无法处理循环引用和剥离函数。

因此,如果您想超越这一点,事情将变得更加复杂,您必须依赖一些实用程序库,或者需要实现自己的深度克隆方法。

下面是一个期望克隆深度级别的示例实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
(function (Object, Array) {
    function cloneObject(deep, scope, clonedScope) {
        var type = typeof this,
            clone = {},
            isCR = -1;

        deep = Number(deep) || 0;
        scope = scope || [];
        clonedScope = clonedScope || [];

        if (!Array.isArray(scope) || !Array.isArray(clonedScope) || clonedScope.length !== scope.length) {
            throw new TypeError("Unexpected input");
        }
        //If we find a primitive, we reeturn its value.
        if (type !=="object") {
            return this.valueOf();
        }

        scope.push(this);
        clonedScope.push(clone);

        if (0 === deep) { //If we reached the recursion limit, we can perform a shallow copy
            for (var prop in this) {
                clone[prop] = this[prop];
            }
        } else { //Otherwise we need to make some checks first.
            for (var prop in this) {
                if ((isCR = scope.indexOf(this[prop])) > -1) { //If we find a circular reference, we want create a new circular reference to the cloned version.
                    clone[prop] = clonedScope[isCR];
                } else if (typeof this[prop] !=="undefined" && this[prop] !== null) { //Otherwise continue cloning.
                    clone[prop] = (typeof this[prop] !=="object" ? this[prop] : this[prop].clone(deep - 1, scope, clonedScope)); //If we find a non object, we can directly assign it. Otherwise we need to recursively call the clone function, counting down the limit, and injecting the scopeArrays, to find circular references.
                } else { //If the property is undefined or null, assign it as such.
                    clone[prop] = this[prop];
                }
            }
        }

        scope.pop(); //If we leave a recursion leve, we remove the current object from the list.
        clonedScope.pop();

        return clone;
    }


    function cloneArray(deep, scope, clonedScope) {
        var clone = [];

        deep = Number(deep) || 0;

        scope = scope || [];
        clonedScope = clonedScope || [];

        if (!Array.isArray(scope) || !Array.isArray(clonedScope) || clonedScope.length !== scope.length) {
            throw new TypeError("Unexpected input");
        }


        scope.push(this);
        clonedScope.push(this);

        if (0 === deep) clone = this.concat();
        else this.forEach(function (e) {
            if ((isCR = scope.indexOf(e)) > -1) {
                clone.push(clonedScope[isCR]);
            } else if (typeof e !=="undefined" && e !== null) {
                clone.push((typeof e !=="object" ? e : e.clone(deep - 1, scope, clonedScope)));
            } else {
                clone.push(e);
            }
        });

        scope.pop();
        clonedScope.pop();

        return clone;
    }

    Object.defineProperty(Object.prototype,"clone", {
        enumerable: false,
        value: cloneObject
    });

    Object.defineProperty(Array.prototype,"clone", {
        enumerable: false,
        value: cloneArray
    });

})(Object, Array);

请注意,扩展内置原型通常是不受欢迎的,但是我决定这样做,以避免额外的类型检查,并将数组和对象的逻辑进一步拆分。这可以很容易地重构为普通函数

一些测试来检查我们是否确实有新的引用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var first = {
    a: {
        b:"b",
        c: {

        }
    },
    b:"asd",
    c: [{}],
    d: undefined,
    e: null,
    f: function a() {} //functions keep their original reference..
};

first.a.c.a = first.a; //Circular object reference
first.c.push(first.c); //Circular array reference

var second = first.clone(Infinity);

console.log(second, second.a === second.a.c.a, first.a !== second.a.c.a); //..., true, true.

可能有很多改进空间,我特别不喜欢Scope和ClonedScope的注入方式。如果有人对查找和重新附加循环引用有更好的想法,我很乐意更新答案

这里还有一把小提琴。


我将使用jquery执行此操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var obj1 = {
     name:"abc",
     age: 20    
}

console.log(obj1);

var obj2 = $.extend({}, obj1, {});
console.log(obj2);

obj2.age = 1;
console.log(obj2);

console.log(obj1);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var obj, objTwo;

obj = {
    name:"abc",
    age: 20
}
console.log(obj.age);
objTwo = copy(obj);
objTwo.age = 10;
console.log(obj.age);

function copy (obj) {
    var key, rtn = Object.create(Object.getPrototypeOf(obj));
    for (key in obj) {
        if (obj.hasOwnProperty(key)) {
            rtn[key] = obj[key];
        }
    }
    return rtn;
}

在JavaScript中,所有内容都是通过引用传递的。修改"泄漏"到原始函数属性(变量)的原因是您正在修改对象的属性,而不是对象(引用)本身。将对象复制到另一个变量,而不是重新分配它。

离题:

在JavaScript中,所有内容都是通过引用传递的。显然有点争议,这就是我添加这个附录的原因。如果我错了,请随时纠正我。

javascript是传递引用还是传递值语言?最上面的答案最清楚地说明了这一点:

Instead, the situation is that the item passed in is passed by value.
But the item that is passed by value is itself a reference.

所以我的措词很混乱,但是如果您还记得每个操作符都返回一个引用,并且记住每个赋值和参数传递只是复制那些由操作符返回的引用(和值文本),那么通过引用传递就有意义了。

链接的最上面的答案有完整(正确)的解释。


好吧,这可能是一个奇怪的解决方案,但它是普通的javascript。如果您想克隆某个东西,我将使用单词"deep copy",您可以这样使用json:

1
2
3
4
5
6
7
var obj = {
    name:"abc",
    age: 20
}


new_object=JSON.parse(JSON.stringify(obj));

现在,您有了对象obj的克隆。

另一种解决方案是:

1
2
3
4
5
6
var new_obj={};
for( new_prop in obj){
    if (obj.hasOwnProperty(new_prop)){
          new_obj[new_prop]=obj[new_prop]
     }
 }