深拷貝和淺拷貝

在 JS 中有一些基本類型像是Number、String、Boolean,而對象就是像這樣的東西{ name: 'Larry', skill: 'Node.js' },對象跟基本類型最大的不同就在于他們的傳值方式。

基本類型是按值傳遞,像是這樣:在修改a時并不會改到b

vara =25;

varb =a;

b=18;

console.log(a);//25

console.log(b);//18

但對象就不同,對象傳的是按引用傳值:

varobj1 = { a:10, b:20, c:30};

varobj2 =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,因為他們根本是同一個對象,這就是所謂的淺拷貝。

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

varobj1 = { a:10, b:20, c:30};

varobj2 ={ 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。

淺拷貝(Shallow Copy) VS 深拷貝(Deep Copy)

淺拷貝只復制指向某個對象的指針,而不復制對象本身,新舊對象還是共享同一塊內存。但深拷貝會另外創造一個一模一樣的對象,新對象跟原對象不共享內存,修改新對象不會改到原對象。

淺拷貝的實現方式

也就是簡單地復制而已

1、簡單地復制語句

function simpleClone(initalObj) {

varobj ={};

for(variininitalObj) {

obj[i]=initalObj[i];

}

returnobj;

}

varobj ={

a:"hello",

b:{

a:"world",

b:21

},

c:["Bob","Tom","Jenny"],

d:function() {

alert("hello world");

}

}

varcloneObj =simpleClone(obj);

console.log(cloneObj.b);

console.log(cloneObj.c);

console.log(cloneObj.d);

cloneObj.b.a="changed";

cloneObj.c= [1,2,3];

cloneObj.d= function() { alert("changed"); };

console.log(obj.b);

console.log(obj.c);

console.log(obj.d);

結果為:

2、Object.assign()

Object.assign是ES6的新函數。Object.assign()方法可以把任意多個的源對象自身的可枚舉屬性拷貝給目標對象,然后返回目標對象。但是Object.assign()進行的是淺拷貝,拷貝的是對象的屬性的引用,而不是對象本身。

Object.assign(target, ...sources)

參數:

target:目標對象。

sources:任意多個源對象。

返回值:目標對象會被返回。

varobj = { a: {a:"hello", b:21} };

varinitalObj =Object.assign({}, obj);

initalObj.a.a="changed";

console.log(obj.a.a);//"changed"

兼容性:

需要注意的是:

Object.assign()可以處理一層的深度拷貝,如下:

varobj1 = { a:10, b:20, c:30};

varobj2 =Object.assign({}, obj1);

obj2.b=100;

console.log(obj1);

//{ a: 10, b: 20, c: 30 } <-- 沒被改到

console.log(obj2);

//{ a: 10, b: 100, c: 30 }

深拷貝的實現方式

要完全復制又不能修改到原對象,這時候就要用?Deep Copy,這里會介紹幾種Deep Copy?的方式。

1、手動復制

把一個對象的屬性復制給另一個對象的屬性

varobj1 = { a:10, b:20, c:30};

varobj2 ={ 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,因為對象里面也可能回事對象,如像下面這個狀況:

varobj1 = { body: { a:10} };

varobj2 ={ 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({}, obj1)的意思是先建立一個空對象{},接著把obj1中所有的屬性復制過去,所以obj2會長得跟obj1一樣,這時候再修改obj2.b也不會影響obj1。

因為Object.assign跟我們手動復制的效果相同,所以一樣只能處理深度只有一層的對象,沒辦法做到真正的?Deep Copy。不過如果要復制的對象只有一層的話可以考慮使用它。

3、轉成?JSON?再轉回來

用JSON.stringify把對象轉成字符串,再用JSON.parse把字符串轉成新的對象。

varobj1 = { body: { a:10} };

varobj2 =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,這種方法簡單易用。

但是這種方法也有不少壞處,譬如它會拋棄對象的constructor。也就是深拷貝之后,不管這個對象原來的構造函數是什么,在深拷貝之后都會變成Object。

這種方法能正確處理的對象只有Number, String, Boolean, Array, 扁平對象,即那些能夠被 json 直接表示的數據結構。RegExp對象是無法通過這種方式深拷貝。

也就是說,只有可以轉成JSON格式的對象才可以這樣用,像function沒辦法轉成JSON。

varobj1 = { fun: function(){ console.log(123) } };

varobj2 =JSON.parse(JSON.stringify(obj1));

console.log(typeofobj1.fun);

//'function'

console.log(typeofobj2.fun);

//'undefined' <-- 沒復制

要復制的function會直接消失,所以這個方法只能用在單純只有數據的對象。

4、遞歸拷貝

function deepClone(initalObj, finalObj) {

varobj = finalObj ||{};

for(variininitalObj) {

if(typeofinitalObj[i] ==='object') {

obj[i]= (initalObj[i].constructor === Array) ?[] : {};

arguments.callee(initalObj[i], obj[i]);

}else{

obj[i]=initalObj[i];

}

}

returnobj;

}

varstr ={};

varobj = { a: {a:"hello", b:21} };

deepClone(obj, str);

console.log(str.a);

上述代碼確實可以實現深拷貝。但是當遇到兩個互相引用的對象,會出現死循環的情況。

為了避免相互引用的對象導致死循環的情況,則應該在遍歷的時候判斷是否相互引用對象,如果是則退出循環。

改進版代碼如下:

function deepClone(initalObj, finalObj) {

varobj = finalObj ||{};

for(variininitalObj) {

varprop = initalObj[i];//避免相互引用對象導致死循環,如initalObj.a = initalObj的情況

if(prop ===obj) {

continue;

}

if(typeofprop ==='object') {

obj[i]= (prop.constructor === Array) ?[] : {};

arguments.callee(prop, obj[i]);

}else{

obj[i]=prop;

}

}

returnobj;

}

varstr ={};

varobj = { a: {a:"hello", b:21} };

deepClone(obj, str);

console.log(str.a);

5、使用Object.create()方法

直接使用var newObj = Object.create(oldObj),可以達到深拷貝的效果。

function deepClone(initalObj, finalObj) {

varobj = finalObj ||{};

for(variininitalObj) {

varprop = initalObj[i];//避免相互引用對象導致死循環,如initalObj.a = initalObj的情況

if(prop ===obj) {

continue;

}

if(typeofprop ==='object') {

obj[i]= (prop.constructor === Array) ?[] : Object.create(prop);

}else{

obj[i]=prop;

}

}

returnobj;

}

6、jquery

jquery?有提供一個$.extend可以用來做?Deep Copy。

var$ = require('jquery');

varobj1 ={

a:1,

b: { f: { g:1} },

c: [1,2,3]

};

varobj2 = $.extend(true, {}, obj1);

console.log(obj1.b.f===obj2.b.f);

//false

7、lodash

另外一個很熱門的函數庫lodash,也有提供_.cloneDeep用來做?Deep Copy。

var_ = require('lodash');

varobj1 ={

a:1,

b: { f: { g:1} },

c: [1,2,3]

};

varobj2 =_.cloneDeep(obj1);

console.log(obj1.b.f===obj2.b.f);

//false

這個性能還不錯,使用起來也很簡單。

最后,又不正之處歡迎之處

原文地址:http://www.cnblogs.com/Chen-XiaoJun/p/6217373.html

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 深拷貝:復制多層 淺拷貝:復制一層 在JavaScript使用過程中我們經常會遇到這樣的場景; 我們發現,當數組b...
    ferrint閱讀 273評論 0 0
  • 對于問題: var obj1 = {name:'小明'}; var obj2 = obj1; obj2.name ...
    藍搖扼劍閱讀 342評論 0 0
  • 深復制和淺復制只針對像 Object, Array 這樣的復雜對象。簡單來說,淺復制只復制一層對象的屬性,而深復制...
    婷樓沐熙閱讀 316評論 0 0
  • 最近在研究lodash的源碼,涉及到深 拷貝和淺拷貝的問題,由此,搜集網上的資料來研究了一番。 共同點: 都是在已...
    劍來___閱讀 153評論 0 0
  • 在做項目的時候遇到了一個問題。對象的復制 例:object1 = object2;發現了修改object1的時候,...
    趙BW閱讀 304評論 0 0