關(guān)于JavaScript的淺拷貝和深拷貝

關(guān)于JavaScript的淺拷貝和深拷貝

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

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

vara =25;varb = a;

b =18;

console.log(a);//25console.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 }

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

要避免這樣的錯誤發(fā)生就要寫成這樣:

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)

淺拷貝只復(fù)制指向某個對象的指針,而不復(fù)制對象本身性含,新舊對象還是共享同一塊內(nèi)存洲赵。但深拷貝會另外創(chuàng)造一個一模一樣的對象,新對象跟原對象不共享內(nèi)存胶滋,修改新對象不會改到原對象板鬓。

淺拷貝的實現(xiàn)方式

也就是簡單地復(fù)制而已

1悲敷、簡單地復(fù)制語句

? ? function simpleClone(initalObj) {? ?

? ? ? varobj = {};? ?

? ? ? for(variin initalObj) {

? ? ? ? obj[i] = initalObj[i];

? ? ? }? ?

? ? ? return obj;

? ? }

? ? 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);


結(jié)果為:

2究恤、Object.assign()

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

Object.assign(target, ...sources)

參數(shù):

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 }

深拷貝的實現(xiàn)方式

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

1筏勒、手動復(fù)制

把一個對象的屬性復(fù)制給另一個對象的屬性

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 }

但這樣很麻煩移迫,要一個一個自己復(fù)制;而且這樣的本質(zhì)也不能算是 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);// falseconsole.log(obj1.body === obj2.body);// true

雖然obj1跟obj2是不同對象,但他們會共享同一個obj1.body捐顷,所以修改obj2.body.a時也會修改到舊的荡陷。

2、對象只有一層的話可以使用上面的:Object.assign()函數(shù)

Object.assign({}, obj1)的意思是先建立一個空對象{}迅涮,接著把obj1中所有的屬性復(fù)制過去废赞,所以obj2會長得跟obj1一樣,這時候再修改obj2.b也不會影響obj1逗柴。

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

3屠尊、轉(zhuǎn)成?JSON?再轉(zhuǎn)回來

用JSON.stringify把對象轉(zhuǎn)成字符串,再用JSON.parse把字符串轉(zhuǎn)成新的對象耕拷。

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);// falseconsole.log(obj1.body === obj2.body);// false

這樣做是真正的Deep Copy讼昆,這種方法簡單易用。

但是這種方法也有不少壞處骚烧,譬如它會拋棄對象的constructor浸赫。也就是深拷貝之后,不管這個對象原來的構(gòu)造函數(shù)是什么赃绊,在深拷貝之后都會變成Object既峡。

這種方法能正確處理的對象只有Number, String, Boolean, Array, 扁平對象,即那些能夠被 json 直接表示的數(shù)據(jù)結(jié)構(gòu)碧查。RegExp對象是無法通過這種方式深拷貝运敢。

也就是說,只有可以轉(zhuǎn)成JSON格式的對象才可以這樣用忠售,像function沒辦法轉(zhuǎn)成JSON传惠。

varobj1 = { fun: function(){ console.log(123) } };varobj2 = JSON.parse(JSON.stringify(obj1));

console.log(typeof obj1.fun);// 'function'console.log(typeof obj2.fun);// 'undefined' <-- 沒復(fù)制

要復(fù)制的function會直接消失,所以這個方法只能用在單純只有數(shù)據(jù)的對象稻扬。

4卦方、遞歸拷貝

function deepClone(initalObj, finalObj) {? ?

? varobj = finalObj || {};? ?

? for(variin initalObj) {? ? ? ?

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

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

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

? ? } else {

? ? ? obj[i] = initalObj[i];

? ? }

? }? ?

? return obj;

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

deepClone(obj, str);

console.log(str.a);

上述代碼確實可以實現(xiàn)深拷貝。但是當遇到兩個互相引用的對象泰佳,會出現(xiàn)死循環(huán)的情況盼砍。

為了避免相互引用的對象導(dǎo)致死循環(huán)的情況,則應(yīng)該在遍歷的時候判斷是否相互引用對象乐纸,如果是則退出循環(huán)衬廷。

改進版代碼如下:

function deepClone(initalObj, finalObj) {? ?

? varobj = finalObj || {};? ?

? for(variin initalObj) {? ? ? ?

? ? varprop = initalObj[i];// 避免相互引用對象導(dǎo)致死循環(huán),如initalObj.a = initalObj的情況if(prop === obj) {? ? ? ? ? ?

? ? ? continue;

? ? }? ? ? ?

? ? if(typeofprop ==='object') {

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

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

? ? } else {

? ? ? obj[i] = prop;

? ? }

? }? ?

? return obj;

}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(variin initalObj) {? ? ? ?

? ? varprop = initalObj[i];// 避免相互引用對象導(dǎo)致死循環(huán)宁昭,如initalObj.a = initalObj的情況if(prop === obj) {? ? ? ? ? ?

? ? ? continue;

? ? }? ? ? ?

? ? if(typeofprop ==='object') {

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

? ? } else {

? ? ? obj[i] = prop;

? ? }

? }? ?

? return obj;

}

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

另外一個很熱門的函數(shù)庫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

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末哎迄,一起剝皮案震驚了整個濱河市回右,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌漱挚,老刑警劉巖翔烁,帶你破解...
    沈念sama閱讀 212,080評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異旨涝,居然都是意外死亡蹬屹,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,422評論 3 385
  • 文/潘曉璐 我一進店門白华,熙熙樓的掌柜王于貴愁眉苦臉地迎上來慨默,“玉大人,你說我怎么就攤上這事弧腥∠萌。” “怎么了?”我有些...
    開封第一講書人閱讀 157,630評論 0 348
  • 文/不壞的土叔 我叫張陵鸟赫,是天一觀的道長蒜胖。 經(jīng)常有香客問我,道長抛蚤,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,554評論 1 284
  • 正文 為了忘掉前任寻狂,我火速辦了婚禮岁经,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蛇券。我一直安慰自己缀壤,他們只是感情好,可當我...
    茶點故事閱讀 65,662評論 6 386
  • 文/花漫 我一把揭開白布纠亚。 她就那樣靜靜地躺著塘慕,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蒂胞。 梳的紋絲不亂的頭發(fā)上图呢,一...
    開封第一講書人閱讀 49,856評論 1 290
  • 那天,我揣著相機與錄音骗随,去河邊找鬼蛤织。 笑死,一個胖子當著我的面吹牛鸿染,可吹牛的內(nèi)容都是我干的指蚜。 我是一名探鬼主播,決...
    沈念sama閱讀 39,014評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼涨椒,長吁一口氣:“原來是場噩夢啊……” “哼摊鸡!你這毒婦竟也來了绽媒?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,752評論 0 268
  • 序言:老撾萬榮一對情侶失蹤免猾,失蹤者是張志新(化名)和其女友劉穎些椒,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體掸刊,經(jīng)...
    沈念sama閱讀 44,212評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡免糕,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,541評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了忧侧。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片石窑。...
    茶點故事閱讀 38,687評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖蚓炬,靈堂內(nèi)的尸體忽然破棺而出松逊,到底是詐尸還是另有隱情,我是刑警寧澤肯夏,帶...
    沈念sama閱讀 34,347評論 4 331
  • 正文 年R本政府宣布经宏,位于F島的核電站,受9級特大地震影響驯击,放射性物質(zhì)發(fā)生泄漏烁兰。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,973評論 3 315
  • 文/蒙蒙 一徊都、第九天 我趴在偏房一處隱蔽的房頂上張望沪斟。 院中可真熱鬧,春花似錦暇矫、人聲如沸主之。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,777評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽槽奕。三九已至,卻和暖如春房轿,著一層夾襖步出監(jiān)牢的瞬間粤攒,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,006評論 1 266
  • 我被黑心中介騙來泰國打工冀续, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留琼讽,地道東北人。 一個月前我還...
    沈念sama閱讀 46,406評論 2 360
  • 正文 我出身青樓洪唐,卻偏偏與公主長得像钻蹬,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子凭需,可洞房花燭夜當晚...
    茶點故事閱讀 43,576評論 2 349

推薦閱讀更多精彩內(nèi)容