摘要:案例中的賦值就是典型的淺拷貝,并且深拷貝與淺拷貝的概念只存在于引用類型。修改修改經(jīng)測(cè)試,也只能實(shí)現(xiàn)一維對(duì)象的深拷貝。經(jīng)過驗(yàn)證,我們發(fā)現(xiàn)提供的自有方法并不能徹底解決的深拷貝問題。
在說深拷貝與淺拷貝前,我們先看兩個(gè)簡(jiǎn)單的案例:
//案例1 var num1 = 1, num2 = num1; console.log(num1) //1 console.log(num2) //1 num2 = 2; //修改num2 console.log(num1) //1 console.log(num2) //2 //案例2 var obj1 = {x: 1, y: 2}, obj2 = obj1; console.log(obj1) //{x: 1, y: 2} console.log(obj2) //{x: 1, y: 2} obj2.x = 2; //修改obj2.x console.log(obj1) //{x: 2, y: 2} console.log(obj2) //{x: 2, y: 2}
按照常規(guī)思維,obj1應(yīng)該和num1一樣,不會(huì)因?yàn)榱硗庖粋€(gè)值的改變而改變,而這里的obj1 卻隨著obj2的改變而改變了。同樣是變量,為什么表現(xiàn)不一樣呢?這就要引入JS中基本類型和引用類型的概念了。
基本類型和引用類型ECMAScript變量可能包含兩種不同數(shù)據(jù)類型的值:基本類型值和引用類型值。基本類型值指的是那些保存在棧內(nèi)存中的簡(jiǎn)單數(shù)據(jù)段,即這種值完全保存在內(nèi)存中的一個(gè)位置。而引用類型值是指那些保存堆內(nèi)存中的對(duì)象,意思是變量中保存的實(shí)際上只是一個(gè)指針,這個(gè)指針指向內(nèi)存中的另一個(gè)位置,該位置保存對(duì)象。
打個(gè)比方,基本類型和引用類型在賦值上的區(qū)別可以按“連鎖店”和“單店”來理解:基本類型賦值等于在一個(gè)新的地方安裝連鎖店的規(guī)范標(biāo)準(zhǔn)新開一個(gè)分店,新開的店與其他舊店互不相關(guān),各自運(yùn)營(yíng);而引用類型賦值相當(dāng)于一個(gè)店有兩把鑰匙,交給兩個(gè)老板同時(shí)管理,兩個(gè)老板的行為都有可能對(duì)一間店的運(yùn)營(yíng)造成影響。
上面清晰明了的介紹了基本類型和引用類型的定義和區(qū)別。目前基本類型有:Boolean、Null、Undefined、Number、String、Symbol,引用類型有:Object、Array、Function。之所以說“目前”,因?yàn)镾ymbol就是ES6才出來的,之后也可能會(huì)有新的類型出來。
再回到前面的案例,案例1中的值為基本類型,案例2中的值為引用類型。案例2中的賦值就是典型的淺拷貝,并且深拷貝與淺拷貝的概念只存在于引用類型。
深拷貝與淺拷貝既然已經(jīng)知道了深拷貝與淺拷貝的來由,那么該如何實(shí)現(xiàn)深拷貝?我們先分別看看Array和Object自有方法是否支持:
Arrayvar arr1 = [1, 2], arr2 = arr1.slice(); console.log(arr1); //[1, 2] console.log(arr2); //[1, 2] arr2[0] = 3; //修改arr2 console.log(arr1); //[1, 2] console.log(arr2); //[3, 2]
此時(shí),arr2的修改并沒有影響到arr1,看來深拷貝的實(shí)現(xiàn)并沒有那么難嘛。我們把a(bǔ)rr1改成二維數(shù)組再來看看:
var arr1 = [1, 2, [3, 4]], arr2 = arr1.slice(); console.log(arr1); //[1, 2, [3, 4]] console.log(arr2); //[1, 2, [3, 4]] arr2[2][1] = 5; console.log(arr1); //[1, 2, [3, 5]] console.log(arr2); //[1, 2, [3, 5]]
咦,arr2又改變了arr1,看來slice()只能實(shí)現(xiàn)一維數(shù)組的深拷貝。
具備同等特性的還有:concat、Array.from() 。
Object Object.assign()var obj1 = {x: 1, y: 2}, obj2 = Object.assign({}, obj1); console.log(obj1) //{x: 1, y: 2} console.log(obj2) //{x: 1, y: 2} obj2.x = 2; //修改obj2.x console.log(obj1) //{x: 1, y: 2} console.log(obj2) //{x: 2, y: 2}
var obj1 = { x: 1, y: { m: 1 } }; var obj2 = Object.assign({}, obj1); console.log(obj1) //{x: 1, y: {m: 1}} console.log(obj2) //{x: 1, y: {m: 1}} obj2.y.m = 2; //修改obj2.y.m console.log(obj1) //{x: 1, y: {m: 2}} console.log(obj2) //{x: 2, y: {m: 2}}
經(jīng)測(cè)試,Object.assign()也只能實(shí)現(xiàn)一維對(duì)象的深拷貝。
JSON.parse(JSON.stringify(obj))var obj1 = { x: 1, y: { m: 1 } }; var obj2 = JSON.parse(JSON.stringify(obj1)); console.log(obj1) //{x: 1, y: {m: 1}} console.log(obj2) //{x: 1, y: {m: 1}} obj2.y.m = 2; //修改obj2.y.m console.log(obj1) //{x: 1, y: {m: 1}} console.log(obj2) //{x: 2, y: {m: 2}}
JSON.parse(JSON.stringify(obj)) 看起來很不錯(cuò),不過MDN文檔 的描述有句話寫的很清楚:
undefined、任意的函數(shù)以及 symbol 值,在序列化過程中會(huì)被忽略(出現(xiàn)在非數(shù)組對(duì)象的屬性值中時(shí))或者被轉(zhuǎn)換成 null(出現(xiàn)在數(shù)組中時(shí))。
我們?cè)賮戆?b>obj1改造下:
var obj1 = { x: 1, y: undefined, z: function add(z1, z2) { return z1 + z2 }, a: Symbol("foo") }; var obj2 = JSON.parse(JSON.stringify(obj1)); console.log(obj1) //{x: 1, y: undefined, z: ?, a: Symbol(foo)} console.log(JSON.stringify(obj1)); //{"x":1} console.log(obj2) //{x: 1}
發(fā)現(xiàn),在將obj1進(jìn)行JSON.stringify()序列化的過程中,y、z、a都被忽略了,也就驗(yàn)證了MDN文檔的描述。既然這樣,那JSON.parse(JSON.stringify(obj))的使用也是有局限性的,不能深拷貝含有undefined、function、symbol值的對(duì)象,不過JSON.parse(JSON.stringify(obj))簡(jiǎn)單粗暴,已經(jīng)滿足90%的使用場(chǎng)景了。
經(jīng)過驗(yàn)證,我們發(fā)現(xiàn)JS 提供的自有方法并不能徹底解決Array、Object的深拷貝問題。只能祭出大殺器:遞歸
function deepCopy(obj) { // 創(chuàng)建一個(gè)新對(duì)象 let result = {} let keys = Object.keys(obj), key = null, temp = null; for (let i = 0; i < keys.length; i++) { key = keys[i]; temp = obj[key]; // 如果字段的值也是一個(gè)對(duì)象則遞歸操作 if (temp && typeof temp === "object") { result[key] = deepCopy(temp); } else { // 否則直接賦值給新對(duì)象 result[key] = temp; } } return result; } var obj1 = { x: { m: 1 }, y: undefined, z: function add(z1, z2) { return z1 + z2 }, a: Symbol("foo") }; var obj2 = deepCopy(obj1); obj2.x.m = 2; console.log(obj1); //{x: {m: 1}, y: undefined, z: ?, a: Symbol(foo)} console.log(obj2); //{x: {m: 2}, y: undefined, z: ?, a: Symbol(foo)}
可以看到,遞歸完美的解決了前面遺留的所有問題,我們也可以用第三方庫:jquery的$.extend和lodash的_.cloneDeep來解決深拷貝。上面雖然是用Object驗(yàn)證,但對(duì)于Array也同樣適用,因?yàn)锳rray也是特殊的Object。
到這里,深拷貝問題基本可以告一段落了。但是,還有一個(gè)非常特殊的場(chǎng)景:
循環(huán)引用拷貝
var obj1 = { x: 1, y: 2 }; obj1.z = obj1; var obj2 = deepCopy(obj1);
此時(shí)如果調(diào)用剛才的deepCopy函數(shù)的話,會(huì)陷入一個(gè)循環(huán)的遞歸過程,從而導(dǎo)致爆棧。jquery的$.extend也沒有解決。解決這個(gè)問題也非常簡(jiǎn)單,只需要判斷一個(gè)對(duì)象的字段是否引用了這個(gè)對(duì)象或這個(gè)對(duì)象的任意父級(jí)即可,修改一下代碼:
function deepCopy(obj, parent = null) { // 創(chuàng)建一個(gè)新對(duì)象 let result = {}; let keys = Object.keys(obj), key = null, temp= null, _parent = parent; // 該字段有父級(jí)則需要追溯該字段的父級(jí) while (_parent) { // 如果該字段引用了它的父級(jí)則為循環(huán)引用 if (_parent.originalParent === obj) { // 循環(huán)引用直接返回同級(jí)的新對(duì)象 return _parent.currentParent; } _parent = _parent.parent; } for (let i = 0; i < keys.length; i++) { key = keys[i]; temp= obj[key]; // 如果字段的值也是一個(gè)對(duì)象 if (temp && typeof temp=== "object") { // 遞歸執(zhí)行深拷貝 將同級(jí)的待拷貝對(duì)象與新對(duì)象傳遞給 parent 方便追溯循環(huán)引用 result[key] = DeepCopy(temp, { originalParent: obj, currentParent: result, parent: parent }); } else { result[key] = temp; } } return result; } var obj1 = { x: 1, y: 2 }; obj1.z = obj1; var obj2 = deepCopy(obj1); console.log(obj1); //太長(zhǎng)了去瀏覽器試一下吧~ console.log(obj2); //太長(zhǎng)了去瀏覽器試一下吧~
至此,已完成一個(gè)支持循環(huán)引用的深拷貝函數(shù)。當(dāng)然,也可以使用lodash的_.cloneDeep噢~。
歡迎討論,點(diǎn)個(gè)贊再走吧~文章同步于以下社區(qū),可以選一個(gè)關(guān)注我噢 ?????
simbawu | github | segmentfault | 知乎 | 簡(jiǎn)書 | 掘金
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/93986.html
摘要:一篇文章徹底說清的深拷貝淺拷貝這篇文章的受眾第一類業(yè)務(wù)需要急需知道如何深拷貝對(duì)象的開發(fā)者。這篇文章分享的目的更多還是希望用一篇文章整理清楚深淺拷貝的含義遞歸實(shí)現(xiàn)思路以及小伙伴們?nèi)绻褂昧诉@種黑科技一定要清楚這樣寫的優(yōu)缺點(diǎn)。 一篇文章徹底說清JS的深拷貝and淺拷貝 這篇文章的受眾 第一類,業(yè)務(wù)需要,急需知道如何深拷貝JS對(duì)象的開發(fā)者。 第二類,希望扎實(shí)JS基礎(chǔ),將來好去面試官前秀操作...
摘要:一篇文章徹底說清的深拷貝淺拷貝這篇文章的受眾第一類業(yè)務(wù)需要急需知道如何深拷貝對(duì)象的開發(fā)者。這篇文章分享的目的更多還是希望用一篇文章整理清楚深淺拷貝的含義遞歸實(shí)現(xiàn)思路以及小伙伴們?nèi)绻褂昧诉@種黑科技一定要清楚這樣寫的優(yōu)缺點(diǎn)。 一篇文章徹底說清JS的深拷貝and淺拷貝 這篇文章的受眾 第一類,業(yè)務(wù)需要,急需知道如何深拷貝JS對(duì)象的開發(fā)者。 第二類,希望扎實(shí)JS基礎(chǔ),將來好去面試官前秀操作...
摘要:一篇文章徹底說清的深拷貝淺拷貝這篇文章的受眾第一類業(yè)務(wù)需要急需知道如何深拷貝對(duì)象的開發(fā)者。這篇文章分享的目的更多還是希望用一篇文章整理清楚深淺拷貝的含義遞歸實(shí)現(xiàn)思路以及小伙伴們?nèi)绻褂昧诉@種黑科技一定要清楚這樣寫的優(yōu)缺點(diǎn)。 一篇文章徹底說清JS的深拷貝and淺拷貝 這篇文章的受眾 第一類,業(yè)務(wù)需要,急需知道如何深拷貝JS對(duì)象的開發(fā)者。 第二類,希望扎實(shí)JS基礎(chǔ),將來好去面試官前秀操作...
摘要:而引用類型值是指那些保存堆內(nèi)存中的對(duì)象,意思是變量中保存的實(shí)際上只是一個(gè)指針,這個(gè)指針指向內(nèi)存中的另一個(gè)位置,該位置保存對(duì)象。而堆內(nèi)存主要負(fù)責(zé)對(duì)象這種變量類型的存儲(chǔ)。我們需要明確一點(diǎn),深拷貝與淺拷貝的概念只存在于引用類型。 深拷貝和淺拷貝 說起深拷貝和淺拷貝,首先我們來看兩個(gè)栗子 // 栗子1 var a = 1,b=a; console.log(a); console.log(b) ...
摘要:而在這個(gè)運(yùn)算符的相關(guān)用例中,往往會(huì)涉及到其他知識(shí)點(diǎn),深拷貝和淺拷貝就是其中之一。即對(duì)象的淺拷貝會(huì)對(duì)主對(duì)象的值進(jìn)行拷貝,而該值有可能是一個(gè)指針,指向內(nèi)存中的同一個(gè)對(duì)象。,可以看到深拷貝和淺拷貝是對(duì)復(fù)制引用類型變量而言的。 在ES6的系列文章中,基本都會(huì)提到Spread——擴(kuò)展運(yùn)算符(...)。而在這個(gè)運(yùn)算符的相關(guān)用例中,往往會(huì)涉及到其他知識(shí)點(diǎn),深拷貝和淺拷貝就是其中之一。 背景知識(shí) 在討...
閱讀 2578·2021-09-30 09:48
閱讀 2568·2019-08-30 14:10
閱讀 2712·2019-08-29 11:22
閱讀 1840·2019-08-26 13:51
閱讀 2279·2019-08-26 12:02
閱讀 2420·2019-08-23 16:06
閱讀 3557·2019-08-23 14:06
閱讀 1096·2019-08-23 13:56