深拷貝和淺拷貝

在 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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,885評論 6 541
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,312評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,993評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,667評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,410評論 6 411
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,778評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,775評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,955評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,521評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,266評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,468評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,998評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,696評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,095評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,385評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,193評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,431評論 2 378

推薦閱讀更多精彩內容

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