對(duì)象的深拷貝與淺拷貝

淺拷貝和深拷貝都是對(duì)于JS中的引用類型而言的酌壕,淺拷貝就只是復(fù)制對(duì)象的引用(堆和棧的關(guān)系燕垃,簡(jiǎn)單類型Undefined箭券,Null茂腥,Boolean狸涌,Number和String是存入堆,直接引用最岗,object array 則是存入桟中帕胆,只用一個(gè)指針來(lái)引用值),如果拷貝后的對(duì)象發(fā)生變化般渡,原對(duì)象也會(huì)發(fā)生變化懒豹。只有深拷貝才是真正地對(duì)對(duì)象的拷貝。

淺拷貝:淺拷貝的意思就是只復(fù)制引用(指針)驯用,而未復(fù)制真正的值脸秽。

const originArray = [1,2,3,4,5];

const originObj = {a:'a',b:'b',c:[1,2,3],d:{dd:'dd'}};

const cloneArray = originArray;

const cloneObj = originObj;

console.log(cloneArray); // [1,2,3,4,5]

console.log(originObj); // {a:'a',b:'b',c:Array[3],d:{dd:'dd'}}

cloneArray.push(6);

cloneObj.a = {aa:'aa'};

console.log(cloneArray); // [1,2,3,4,5,6]

console.log(originArray); // [1,2,3,4,5,6]

console.log(cloneObj); // {a:{aa:'aa'},b:'b',c:Array[3],d:{dd:'dd'}}

console.log(originArray); // {a:{aa:'aa'},b:'b',c:Array[3],d:{dd:'dd'}}

上面的代碼是最簡(jiǎn)單的利用=賦值操作符實(shí)現(xiàn)了一個(gè)淺拷貝,可以很清楚的看到蝴乔,隨著cloneArray和cloneObj改變记餐,originArray和originObj也隨著發(fā)生了變化。

深拷貝:深拷貝就是對(duì)目標(biāo)的完全拷貝薇正,不像淺拷貝那樣只是復(fù)制了一層引用剥扣,就連值也都復(fù)制了巩剖。

只要進(jìn)行了深拷貝,它們老死不相往來(lái)钠怯,誰(shuí)也不會(huì)影響誰(shuí)佳魔。

目前實(shí)現(xiàn)深拷貝的方法不多,主要是兩種:

利用?JSON?對(duì)象中的?parse?和?stringify

利用遞歸來(lái)實(shí)現(xiàn)每一層都重新創(chuàng)建對(duì)象并賦值

JSON.stringify/parse的方法

先看看這兩個(gè)方法吧:

The JSON.stringify() method converts a JavaScript value to a JSON string.

JSON.stringify是將一個(gè)JavaScript值轉(zhuǎn)成一個(gè)JSON字符串晦炊。

The JSON.parse() method parses a JSON string, constructing the JavaScript value or object described by the string.

JSON.parse是將一個(gè)JSON字符串轉(zhuǎn)成一個(gè)JavaScript值或?qū)ο蟆?/p>

很好理解吧鞠鲜,就是JavaScript值和JSON字符串的相互轉(zhuǎn)換。

它能實(shí)現(xiàn)深拷貝呢断国?我們來(lái)試試贤姆。

const originArray = [1,2,3,4,5];

const cloneArray = JSON.parse(JSON.stringify(originArray));

console.log(cloneArray === originArray); // false

const originObj = {a:'a',b:'b',c:[1,2,3],d:{dd:'dd'}};

const cloneObj = JSON.parse(JSON.stringify(originObj));

console.log(cloneObj === originObj); // false

cloneObj.a = 'aa';

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

cloneObj.d.dd = 'doubled';

console.log(cloneObj); // {a:'aa',b:'b',c:[1,1,1],d:{dd:'doubled'}};

console.log(originObj); // {a:'a',b:'b',c:[1,2,3],d:{dd:'dd'}};

 確實(shí)是深拷貝,也很方便稳衬。但是霞捡,這個(gè)方法只能適用于一些簡(jiǎn)單的情況。比如下面這樣的一個(gè)對(duì)象就不適用:

const originObj = {

? name:'axuebin',

? sayHello:function(){

? ? console.log('Hello World');

? }

}

console.log(originObj); // {name: "axuebin", sayHello: ?}

const cloneObj = JSON.parse(JSON.stringify(originObj));

console.log(cloneObj); // {name: "axuebin"}

發(fā)現(xiàn)在cloneObj中薄疚,有屬性丟失了碧信。。街夭。那是為什么呢砰碴?

undefined燎窘、function涝桅、symbol會(huì)在轉(zhuǎn)換過程中被忽略崔列。辈末。。

If undefined, a function, or a symbol is encountered during conversion it is either omitted (when it is found in an object) or censored to null (when it is found in an array). JSON.stringify can also just return undefined when passing in "pure" values like JSON.stringify(function(){}) or JSON.stringify(undefined).

  明白了吧旅挤,就是說如果對(duì)象中含有一個(gè)函數(shù)時(shí)(很常見)切威,就不能用這個(gè)方法進(jìn)行深拷貝

遞歸的方法

遞歸的思想就很簡(jiǎn)單了伍宦,就是對(duì)每一層的數(shù)據(jù)都實(shí)現(xiàn)一次?創(chuàng)建對(duì)象->對(duì)象賦值的操作砚殿,簡(jiǎn)單粗暴上代碼:

function deepClone(source){

? const targetObj = source.constructor === Array ? [] : {}; // 判斷復(fù)制的目標(biāo)是數(shù)組還是對(duì)象

? for(let keys in source){ // 遍歷目標(biāo)

? ? if(source.hasOwnProperty(keys)){

? ? ? if(source[keys] && typeof source[keys] === 'object'){ // 如果值是對(duì)象啃憎,就遞歸一下

? ? ? ? targetObj[keys] = source[keys].constructor === Array ? [] : {};

? ? ? ? targetObj[keys] = deepClone(source[keys]);

? ? ? }else{ // 如果不是,就直接賦值

? ? ? ? targetObj[keys] = source[keys];

? ? ? }

? ? }

? }

? return targetObj;

}

  我們來(lái)試試:

const originObj = {a:'a',b:'b',c:[1,2,3],d:{dd:'dd'}};

const cloneObj = deepClone(originObj);

console.log(cloneObj === originObj); // false

cloneObj.a = 'aa';

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

cloneObj.d.dd = 'doubled';

console.log(cloneObj); // {a:'aa',b:'b',c:[1,1,1],d:{dd:'doubled'}};

console.log(originObj); // {a:'a',b:'b',c:[1,2,3],d:{dd:'dd'}};

可以瓮具。那再試試帶有函數(shù)的:

const originObj = {

? name:'axuebin',

? sayHello:function(){

? ? console.log('Hello World');

? }

}

console.log(originObj); // {name: "axuebin", sayHello: ?}

const cloneObj = deepClone(originObj);

console.log(cloneObj); // {name: "axuebin", sayHello: ?}

  也可以荧飞。搞定凡人。

JavaScript中的拷貝方法

我們知道在?JavaScript?中名党,數(shù)組有兩個(gè)方法?concat?和?slice?是可以實(shí)現(xiàn)對(duì)原數(shù)組的拷貝的,這兩個(gè)方法都不會(huì)修改原數(shù)組挠轴,而是返回一個(gè)修改后的新數(shù)組传睹。

同時(shí),ES6 中 引入了?Object.assgn?方法和?...?展開運(yùn)算符也能實(shí)現(xiàn)對(duì)對(duì)象的拷貝岸晦。

那它們是淺拷貝還是深拷貝呢欧啤?

concat

The concat() method is used to merge two or more arrays. This method does not change the existing arrays, but instead returns a new array.

該方法可以連接兩個(gè)或者更多的數(shù)組睛藻,但是它不會(huì)修改已存在的數(shù)組,而是返回一個(gè)新數(shù)組邢隧。

看著這意思店印,很像是深拷貝啊,我們來(lái)試試:

const originArray = [1,2,3,4,5];

const cloneArray = originArray.concat();

console.log(cloneArray === originArray); // false

cloneArray.push(6); // [1,2,3,4,5,6]

console.log(originArray); [1,2,3,4,5];

看上去是深拷貝的倒慧。

我們來(lái)考慮一個(gè)問題按摘,如果這個(gè)對(duì)象是多層的,會(huì)怎樣纫谅。

const originArray = [1,[1,2,3],{a:1}];

const cloneArray = originArray.concat();

console.log(cloneArray === originArray); // false

cloneArray[1].push(4);

cloneArray[2].a = 2;

console.log(originArray); // [1,[1,2,3,4],{a:2}]

originArray?中含有數(shù)組?[1,2,3]?和對(duì)象?{a:1}炫贤,如果我們直接修改數(shù)組和對(duì)象,不會(huì)影響?originArray付秕,但是我們修改數(shù)組?[1,2,3]?或?qū)ο?{a:1}?時(shí)兰珍,發(fā)現(xiàn)?originArray?也發(fā)生了變化。

結(jié)論:concat?只是對(duì)數(shù)組的第一層進(jìn)行深拷貝询吴。

slice

The slice() method returns a shallow copy of a portion of an array into a new array object selected from begin to end (end not included). The original array will not be modified.

解釋中都直接寫道是a shallow copy了 ~

但是掠河,并不是!

const originArray = [1,2,3,4,5]; const cloneArray = originArray.slice(); console.log(cloneArray === originArray); // false cloneArray.push(6); // [1,2,3,4,5,6] console.log(originArray); [1,2,3,4,5];

  同樣地汰寓,我們?cè)囋嚩鄬拥臄?shù)組口柳。

const originArray = [1,[1,2,3],{a:1}];

const cloneArray = originArray.slice();

console.log(cloneArray === originArray); // false

cloneArray[1].push(4);

cloneArray[2].a = 2;

console.log(originArray); // [1,[1,2,3,4],{a:2}]

果然,結(jié)果和concat是一樣的有滑。

結(jié)論:slice只是對(duì)數(shù)組的第一層進(jìn)行深拷貝跃闹。

Object.assign()

The Object.assign() method is used to copy the values of all enumerable own properties from one or more source objects to a target object. It will return the target obj

結(jié)論:Object.assign()拷貝的是屬性值。假如源對(duì)象的屬性值是一個(gè)指向?qū)ο蟮囊妹茫仓豢截惸莻€(gè)引用值望艺。

... 展開運(yùn)算符

const originArray = [1,2,3,4,5,[6,7,8]];

const originObj = {a:1,b:{bb:1}};

const cloneArray = [...originArray];

cloneArray[0] = 0;

cloneArray[5].push(9);

console.log(originArray); // [1,2,3,4,5,[6,7,8,9]]

const cloneObj = {...originObj};

cloneObj.a = 2;

cloneObj.b.bb = 2;

console.log(originObj); // {a:1,b:{bb:2}}

結(jié)論:...實(shí)現(xiàn)的是對(duì)象第一層的深拷貝。后面的只是拷貝的引用值肌访。

首層淺拷貝

我們知道了找默,會(huì)有一種情況,就是對(duì)目標(biāo)對(duì)象的第一層進(jìn)行深拷貝吼驶,然后后面的是淺拷貝惩激,可以稱作“首層淺拷貝”。

我們可以自己實(shí)現(xiàn)一個(gè)這樣的函數(shù):

function shallowClone(source) {

? const targetObj = source.constructor === Array ? [] : {}; // 判斷復(fù)制的目標(biāo)是數(shù)組還是對(duì)象

? for (let keys in source) { // 遍歷目標(biāo)

? ? if (source.hasOwnProperty(keys)) {

? ? ? targetObj[keys] = source[keys];

? ? }

? }

? return targetObj;

}

  我們來(lái)測(cè)試一下:

const originObj = {a:'a',b:'b',c:[1,2,3],d:{dd:'dd'}};

const cloneObj = shallowClone(originObj);

console.log(cloneObj === originObj); // false

cloneObj.a='aa';

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

cloneObj.d.dd='surprise';

經(jīng)過上面的修改蟹演,cloneObj?不用說风钻,肯定是?{a:'aa',b:'b',c:[1,1,1],d:{dd:'surprise'}}?了,那?originObj?呢酒请?剛剛我們驗(yàn)證了?cloneObj === originObj?是?false骡技,說明這兩個(gè)對(duì)象引用地址不同啊,那應(yīng)該就是修改了?cloneObj?并不影響?originObj。

console.log(cloneObj); // {a:'aa',b:'b',c:[1,1,1],d:{dd:'surprise'}}

console.log(originObj); // {a:'a',b:'b',c:[1,2,3],d:{dd:'surprise'}}

What happend?

originObj?中關(guān)于?a布朦、c都沒被影響囤萤,但是?d?中的一個(gè)對(duì)象被修改了。是趴。涛舍。說好的深拷貝呢?不是引用地址都不一樣了嗎唆途?

原來(lái)是這樣:

從?shallowClone?的代碼中我們可以看出做盅,我們只對(duì)第一層的目標(biāo)進(jìn)行了?深拷貝?,而第二層開始的目標(biāo)我們是直接利用?=?賦值操作符進(jìn)行拷貝的窘哈。

so吹榴,第二層后的目標(biāo)都只是復(fù)制了一個(gè)引用,也就是淺拷貝滚婉。

總結(jié)

賦值運(yùn)算符=實(shí)現(xiàn)的是淺拷貝图筹,只拷貝對(duì)象的引用值;

JavaScript 中數(shù)組和對(duì)象自帶的拷貝方法都是“首層淺拷貝”让腹;

JSON.stringify實(shí)現(xiàn)的是深拷貝远剩,但是對(duì)目標(biāo)對(duì)象有要求(非 undefined,function)骇窍;

若想真正意義上的深拷貝瓜晤,請(qǐng)遞歸。

本文轉(zhuǎn)載自:https://www.cnblogs.com/dabingqi/p/8502932.html

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末腹纳,一起剝皮案震驚了整個(gè)濱河市痢掠,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌嘲恍,老刑警劉巖足画,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異佃牛,居然都是意外死亡淹辞,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門俘侠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)象缀,“玉大人,你說我怎么就攤上這事爷速⊙胄牵” “怎么了?”我有些...
    開封第一講書人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵遍希,是天一觀的道長(zhǎng)等曼。 經(jīng)常有香客問我里烦,道長(zhǎng)凿蒜,這世上最難降的妖魔是什么禁谦? 我笑而不...
    開封第一講書人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮废封,結(jié)果婚禮上州泊,老公的妹妹穿的比我還像新娘。我一直安慰自己漂洋,他們只是感情好遥皂,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著刽漂,像睡著了一般演训。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上贝咙,一...
    開封第一講書人閱讀 48,970評(píng)論 1 284
  • 那天样悟,我揣著相機(jī)與錄音,去河邊找鬼庭猩。 笑死窟她,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的蔼水。 我是一名探鬼主播震糖,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼趴腋!你這毒婦竟也來(lái)了吊说?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤优炬,失蹤者是張志新(化名)和其女友劉穎疏叨,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體穿剖,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蚤蔓,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了糊余。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片秀又。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖贬芥,靈堂內(nèi)的尸體忽然破棺而出吐辙,到底是詐尸還是另有隱情,我是刑警寧澤蘸劈,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布昏苏,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏贤惯。R本人自食惡果不足惜洼专,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望孵构。 院中可真熱鬧屁商,春花似錦、人聲如沸颈墅。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)恤筛。三九已至官还,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間毒坛,已是汗流浹背妻枕。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留粘驰,地道東北人屡谐。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像蝌数,于是被迫代替她去往敵國(guó)和親愕掏。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

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