浅谈JavaScript浅拷贝和深拷贝
网上关于这个话题,讨论有很多了,根据各路情况我自己整理了一下,最后还是能接近完美的实现深拷贝,欢迎大家讨论。
javascript
中的对象是引用类型,在复制对象的时候就要考虑是用浅拷贝还是用深拷贝。
一、直接赋值
对象是引用类型,如果直接赋值给另外一个对象,那么只是赋值一个引用,实际上两个变量指向的同一个数据对象,如果其中一个对象的属性变更,那么另外一个也会变更。
示例1,简单示例:
let human1 = { id: 1, name: "happy" }; human2 = human1; // 这里就是直接赋值 console.log(human1); // {id: 1, name: 'happy'} console.log(human2); // {id: 1, name: 'happy'} // 更改human1的名称,human2的也会更改 human1.name = "life"; console.log(human1); // {id: 1, name: 'life'} console.log(human2); // {id: 1, name: 'life'}
示例2,对象作为参数传递也是传递引用:
let human1 = { id: 1, name: "happy" }; console.log(human1); // {id: 1, name: 'happy'} function foo(human) { // 这里更改了human对象的名称 human.name = "life"; } foo(human1); // 传递对象是传递引用 console.log(human1); // {id: 1, name: 'life'}
二、浅拷贝
浅拷贝只是复制了对象的第一层,如果第一层的属性值是对象,那么该属性也只是复制了一个引用。
let object1 = { a: 1, b: { // b是对象 b1: 2 } }; object2 = object.assign({}, object1); // 这里就是浅拷贝,其中的b对象只复制了引用 // a是常规类型,不会互相影响 object1.a = 10; console.log(object1.a); // 10 console.log(object2.a); // 1 // b是对象,会互相影响 object1.b.b1 = 20; console.log(object1.b.b1); // 20 console.log(object2.b.b1); // 20
如果要实现完整的复制,就要使用深拷贝。
三、深拷贝
森拷贝就是不光是一层要复制,里面的层如果是对象也要进行复制。
1. json对象的方式
如果对象可以确认是json
对象,那么可以用json
对象的方式。
沿用上面的例子:
let object1 = { a: 1, b: { // b是对象 b1: 2 } }; object2 = json.parse(json.stringify(object1)); // 深拷贝 // a是常规类型,不会互相影响 object1.a = 10; console.log(object1.a); // 10 console.log(object2.a); // 1 // b是对象,也不会互相影响 object1.b.b1 = 20; console.log(object1.b.b1); // 20 console.log(object2.b.b1); // 2
这边深拷贝的原理其实就是先把对象转成json
字符串,然后再转成json
对象,中间转成json
字符串后就和原来的对象没有关系了。
这方法的优点:实现非常简单。
缺点:
如果有属性值是函数的话,那么无法进行复制,数据会丢失。
另外原型对象无法进行复制。
所以这种方式只适合对象确认是一个纯粹的json
数据。
2. 递归复制
因为需要一层一层递进复制,很容想到用递归的方式,参考如下实现:
function deepcopy(source) { // 如果不是对象或者是null则直接返回 if (typeof source !== 'object' || source === null) { return source; } let target = {}; // 遍历复制属性 for (let k in source) { if (!source.hasownproperty(k)) { continue; } if (typeof source[k] === 'object') { // 如果是对象,则递归复制 target[k] = deepcopy(source[k]); continue; } let descriptor = object.getownpropertydescriptor(source, k); object.defineproperty(target, k, descriptor); } return target; }
因为是一层一层复制,所以复制完成后,两个对象不会互相影响,并且也可以支持方法。
let object1 = { a: 1, b: { // b是对象 b1: 2 }, f: function() { // f是方法 console.log(3); } }; object2 = deepcopy(object1); // 深拷贝,也可以复制函数了。 object1.f(); // 3 object2.f(); // 3 // b是对象,也不会互相影响 object1.b.b1 = 20; console.log(object1.b.b1); // 20 console.log(object2.b.b1); // 2
复制原型对象
但是这个方法还存在一个问题,就是原型对象无法复制,稍微改进一下:
// 把 let target = {}; 改成如下方式 // 保证原型也进行了复制 let target = object.create(object.getprototypeof(source));
这样就可以了,来个示例验证一下:
function human() { this.id = 1; } human.prototype.bar = function() { console.log("bar"); }; let human1 = new human(); human2 = deepcopy(human1); console.log("human1", human1); console.log("human2", human2);
查看下两个对象的原型:
深拷贝复制原型对象:
完美复制。
当然这样的方法也存在一个问题,就是递归本身存在如果层次过深,容易造成栈溢出的问题。但是在实务中也建议不要复制非常大的对象,应该有另外好的解决方法。
到此这篇关于浅谈javascript
浅拷贝和深拷贝的文章就介绍到这了,更多相关javascript
浅拷贝和深拷贝内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!
参考文档:
js实现深拷贝:https://www.cnblogs.com/dobeco/p/11295316.html
object.assign():https://developer.mozilla.org/zh-cn/docs/web/javascript/reference/global_objects/object/assign
object.create():https://developer.mozilla.org/zh-cn/docs/web/javascript/reference/global_objects/object/create
object.getprototypeof():https://developer.mozilla.org/zh-cn/docs/web/javascript/reference/global_objects/object/getprototypeof
object.defineproperty():https://developer.mozilla.org/zh-cn/docs/web/javascript/reference/global_objects/object/defineproperty
object.getownpropertydescriptor():https://developer.mozilla.org/zh-cn/docs/web/javascript/reference/global_objects/object/getownpropertydescriptor
hasownproperty():https://developer.mozilla.org/zh-cn/docs/web/javascript/reference/global_objects/object/hasownproperty
上一篇: 面向对象-OO