淺拷貝:把字符串、數字的值賦值給新變量,相當於把值完全復制過去,新變量的值改變不會影響舊變量。但是對象卻不同,因為是復制的地址,所以新的值改變也會影響原來的值
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