1. 程式人生 > >深拷貝與淺拷貝

深拷貝與淺拷貝

post false 所有 console oda 嵌套 lod cti 屬性。

淺拷貝

對於基本類型,淺拷貝是對值的復制,對於對象來說,淺拷貝只復制指向某個對象的指針,而不復制對象本身,並沒有開辟新的棧,也就是復制的結果是新舊對象還是共享同一塊內存,兩個對象指向同一個地址,修改其中一個對象的屬性,則另一個對象的屬性也會改變。

深拷貝

深拷貝會開辟新的棧,創造一個一模一樣的對象,兩個對象對應兩個不同的地址,不共享內存,修改一個對象的屬性,不會改變另一個對象的屬性。

在js中,對象跟基本類型最大的不同就在於他們的傳值方式。

基本類型是傳 value,如下:

var a = 10;
var b = a;
b = 20;
console.log(a);   //10
console.log(b);    //
20

在修改a時並不會改到b。

但對象就不同,對象傳的是reference:

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

復制一份obj1叫做obj2,然後把obj2.b改成100,但卻不小心改到obj1.b,因為他們根本是同一個對象,這就是所謂的淺拷貝。

要避免這樣的錯誤發生就要寫成這樣:

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 } <-- b 沒被改到 console.log(obj2); // { a: 10, b: 100, c: 30 }

這樣就是深拷貝,不會改到原本的obj1

如何做到深拷貝

要完全復制又不能修改到原對象,這時候就要用深拷貝,下面是深拷貝的幾種方式:

1、自己手動復制

像上面的範例,把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 }

但這樣很麻煩要自己慢慢復制,而且這樣其實不是深拷貝,如果像下面這個狀況。

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時也會修改到舊的。

2、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跟我們手動復制的效果相同,所以一樣只能處理深度只有一層的對象,沒辦法做到真正的深拷貝,不過如果要復制的對象只有一層的話可以考慮使用它。

如下有嵌套對象時,則沒有辦法做到深拷貝:

    var obj1 = { a: 0 , b: { c: 0}};
    var obj2 = Object.assign({}, obj1);
    console.log(obj2); // { a: 0, b: { c: 0}}

    obj1.a = 1;
    console.log(obj1);  // { a: 1, b: { c: 0}}

    console.log(obj2);   // { a: 0, b: { c: 0}}

    obj2.a = 2;
    console.log(obj1);    // { a: 1, b: { c: 0}}

    console.log(obj2);    // { a: 2, b: { c: 0}}

    obj2.b.c = 3;
    console.log(obj1);    // { a: 1, b: { c: 3}}

    console.log(obj2);    // { a: 2, b: { c: 3}}

3、轉成 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會直接消失,所以這個方法只能用在單純只有數據的對象。

4、jquery的$.extend

jquery 有提供一個$.extend可以用來做深拷貝:

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

5、lodash的_.cloneDeep
函數庫lodash提供_.cloneDeep用來做深拷貝:

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

這是比較推薦的方法,性能還不錯,使用起來也很簡單。

深拷貝與淺拷貝