深拷贝与浅拷贝及其实现方式

时间:2023-01-05 19:47:43

浅拷贝:把字符串、数字的值赋值给新变量,相当于把值完全复制过去,新变量的值改变不会影响旧变量。但是对象却不同,因为是复制的地址,所以新的值改变也会影响原来的值

var m = { a: 10, b: 20 }
var n = m;
n.a = 15;
// 这时m.a的值是多少

m.a会输出15,因为这是浅拷贝,n和m指向的是同一个堆,对象复制只是复制的对象的引用

深拷贝:对象的赋值会相互影响,而数字,字符串之类的不会,我们将对象遍历,在数字、字符串将其对应赋值,这就是一般深拷贝的方式

var m = { a: 10, b: 20 }
var n = {a:m.a,b:m.b};
n.a = 15;

这时的m.a的值还是10,并没有改变,m对象和n对象虽然所有的值都是一样的,但在堆里面,对应的不是同一个了,这个就是深拷贝

深拷贝的实现方式

自己手动复制

像上面的范例,把obj1的属性一个个复制到obj2中:

var obj1 = { a: 10, b: 20, c: 30 };
var obj2 = { a: obj1.a, b: obj1.b, c: obj1.c };
obj2.b = 100;
console.log(obj1);
// { a: 10, b: 20, c: 30 } <-- 沒被改到
console.log(obj2);
// { a: 10, b: 100, c: 30 }

但这样很麻烦要自己慢慢复制,而且这样其实不是 Deep Copy,如果像下面这个状况

var obj1 = { body: { a: 10 } };
var obj2 = { body: obj1.body };
obj2.body.a = 20;
console.log(obj1);
// { body: { a: 20 } } <-- 被改到了
console.log(obj2);
// { body: { a: 20 } }
console.log(obj1 === obj2);
// false
console.log(obj1.body === obj2.body);
// true

虽然obj1跟obj2是不同对象,但他们会共享同一个obj1.body,所以修改obj2.body.a时也会修改到旧的。

Object.assign

Object.assign是 ES6 的新函数,可以帮助我们达成跟上面一样的功能。

var obj1 = { a: 10, b: 20, c: 30 };
var obj2 = Object.assign({}, obj1);
obj2.b = 100;
console.log(obj1);
// { a: 10, b: 20, c: 30 } <-- 沒被改到
console.log(obj2);
// { a: 10, b: 100, c: 30 }

Object.assign({}, obj1)的意思是先建立一个空对象{},接着把obj1中所有的属性复制过去,所以obj2会长得跟obj1一样,这时候再修改obj2.b也不会影响obj1。

因为Object.assign跟我们手动复制的效果相同,所以一样只能处理深度只有一层的对象,没办法做到真正的 Deep Copy。不过如果要复制的对象只有一层的话可以考虑使用它。

转成 JSON 再转回来

用JSON.stringify把对象转成字符串,再用JSON.parse把字符串转成新的对象

var obj1 = { body: { a: 10 } };
var obj2 = JSON.parse(JSON.stringify(obj1));
obj2.body.a = 20;
console.log(obj1);
// { body: { a: 10 } } <-- 沒被改到
console.log(obj2);
// { body: { a: 20 } }
console.log(obj1 === obj2);
// false
console.log(obj1.body === obj2.body);
// false

这样做是真正的Deep Copy,但只有可以转成JSON格式的对象才可以这样用,像function没办法转成JSON。

var obj1 = { fun: function(){ console.log(123) } };
var obj2 = JSON.parse(JSON.stringify(obj1));
console.log(typeof obj1.fun);
// 'function'
console.log(typeof obj2.fun);
// 'undefined' <-- 没复制

要复制的function会直接消失,所以这个方法只能用在单纯只有数据的对象。

jquery

jquery 有提供一个$.extend可以用来做 Deep Copy。

var $ = require('jquery');
var obj1 = {
    a: 1,
    b: { f: { g: 1 } },
    c: [1, 2, 3]
};
var obj2 = $.extend(true, {}, obj1);
console.log(obj1.b.f === obj2.b.f);
// false

lodash

另外一个很热门的函数库lodash,也有提供_.cloneDeep用来做 Deep Copy

var _ = require('lodash');
var obj1 = {
    a: 1,
    b: { f: { g: 1 } },
    c: [1, 2, 3]
};
var obj2 = _.cloneDeep(obj1);
console.log(obj1.b.f === obj2.b.f);
// false

参考文章

关于 JS 中的浅拷贝和深拷贝