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]; } |
查看小提琴
如果只需要克隆简单对象,只需执行
就够了。
但这显然不适用于所有情况,因为
因此,如果您想超越这一点,事情将变得更加复杂,您必须依赖一些实用程序库,或者需要实现自己的深度克隆方法。
下面是一个期望克隆深度级别的示例实现。
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] } } |