摘要:從而也引出了所謂的深淺復(fù)制問題。附注對(duì)于淺復(fù)制,其實(shí)還有其他的實(shí)現(xiàn)方式,比如數(shù)組中和方法,對(duì)于這些還是希望大家自己了解,本本主要針對(duì)深淺復(fù)制的實(shí)現(xiàn)原理進(jìn)行解析。
前言
在之前寫繼承的過程談到了深淺復(fù)制的問題,因?yàn)橛凶x者反映到需要解析,趁今天周末寫一篇解析,今天的主體相對(duì)之前來說理解難度低一些,篇幅可能也比較短,諸君按需閱讀即可。
從兩種數(shù)據(jù)類型說起在js中,變量的類型可以大致分成兩種:基本數(shù)據(jù)類型和引用數(shù)據(jù)類型,其中基本數(shù)據(jù)類型指的是簡單的數(shù)據(jù)段,包括:
Undefined
Null
Boolean
Number
String(字符串在一些其他語言中是被當(dāng)做對(duì)象使用的,屬于引用類型,但在js里是基本類型)
而引用類型的值指的是可能包含多個(gè)值的對(duì)象。可能上面這種描述大家都看過不少,但是有沒有思考過為什么要把數(shù)據(jù)類型這樣分呢?本質(zhì)上,是因?yàn)榛緮?shù)據(jù)類型保存在棧內(nèi)存,而引用類型保存在堆內(nèi)存中。那再進(jìn)一步問:為什么要分兩種保存方式呢? 根本原因在于保存在棧內(nèi)存的必須是大小固定的數(shù)據(jù),引用類型的大小不固定,只能保存在堆內(nèi)存中,但是我們可以把它的地址寫在占內(nèi)存中以供我們?cè)L問。舉個(gè)例子:
var a = 1;//定義了一個(gè)number類型 var obj1 = {//定義了一個(gè)objr類型 name:"obj" };
在執(zhí)行這段代碼后,內(nèi)存空間里是這樣的:
因?yàn)檫@種保存方式的存在,所以我們?cè)诓僮髯兞康臅r(shí)候,如果是基本數(shù)據(jù)類型,則按值訪問,操作的就是變量保存的值;如果是引用類型的值,我們只是通過保存在變量中的引用類型的地址類操作實(shí)際對(duì)象。從而也引出了所謂的深淺復(fù)制問題。
緊接著上文的內(nèi)容,假設(shè)有以下代碼:
//例子1 var a = 1; var b = a;//復(fù)制 console.log(b)//1 a = 2;//改變a的值 console.log(b)//1
可以看到,我們復(fù)制完b以后,即使改變a的值,b也不會(huì)改變,因?yàn)閍和b是相互獨(dú)立的,按照上面的圖,也就是在棧內(nèi)存中創(chuàng)建了一個(gè)變量b 保存的值也是2;
//例子2 var color1 = ["red","green"]; var color2 = color1;//復(fù)制 console.log(color2)//["red","green"]; color1.push("black") ;//改變color1的值 console.log(color2)//["red","green","black"]
在例子2中,我們按照完全相同的步驟,操作了一個(gè)數(shù)組,但是返回的結(jié)果卻完全不一樣,因?yàn)榇藭r(shí)的復(fù)制,實(shí)際上是這樣:
我們只是復(fù)制了一次引用類型的地址而已,所以,不管接下來我們是操作color1還是color2,本質(zhì)上都是操作同一個(gè)數(shù)組對(duì)象。
剛剛說到,簡單的賦值沒有辦法復(fù)制引用類型,那如果我們就是想復(fù)制上面的color1數(shù)組怎么辦呢?可以這樣:
var color1 = ["red","green"]; var color2 = []; //復(fù)制 for(var i = 0;i < color1.length;i++){ color2[i] = color1[i]; } console.log(color2)//["red","green"]; color1.push("black") ;//改變color1的值 console.log(color2)//["red","green"]
這一次我們先創(chuàng)建了一個(gè)空數(shù)組color2,然后讓color2的每個(gè)值都和color1對(duì)應(yīng)相等,最后的color1和color2是相互獨(dú)立的了,滿足了我們的需要。當(dāng)然對(duì)于對(duì)象類型也是一樣的,使用for-in遍歷取代這里的for循環(huán)即可。
問題真的就這樣解決了嗎?當(dāng)然沒有,不過以上這種只復(fù)制了第一層屬性的方式就叫做淺復(fù)制,淺復(fù)制有什么缺陷呢?我們可以先思考一下,從直接使用=符號(hào)賦值進(jìn)行復(fù)制到淺復(fù)制,能夠復(fù)制成功(成功是指復(fù)制的結(jié)果與復(fù)制源完全獨(dú)立),是因?yàn)槲覀儚?fù)制的對(duì)象都是基本類型,怎么解釋呢?
在復(fù)制基本數(shù)據(jù)類型時(shí),我們直接使用=完成復(fù)制
在引用類型的時(shí)候,我們循環(huán)遍歷對(duì)象,對(duì)每個(gè)屬性或值使用=完成復(fù)制
有沒有注意到上文的color1例子使用淺復(fù)制之所以能夠復(fù)制成功,是因?yàn)?strong>數(shù)組中的每一項(xiàng)都是基本數(shù)據(jù)類型(string),所以猜出了淺復(fù)制的局限了嗎?假如數(shù)組中某一項(xiàng)保存的是一個(gè)對(duì)象,或者是一個(gè)數(shù)組,又或者對(duì)象的某個(gè)屬性還是一個(gè)對(duì)象呢?(換句話說就是引用類型的某個(gè)屬性還是引用類型),如:
var person = { name:"lin", score:{ physics:85, math:99 } }
這個(gè)對(duì)象的分?jǐn)?shù)score屬性就還是一個(gè)對(duì)象,那我們使用前面提到for-in遍歷復(fù)制的時(shí)候,對(duì)score的復(fù)制,不就又變成了我們前面提到的只復(fù)制了地址的情況嗎?再想想淺復(fù)制實(shí)現(xiàn)的原理,相信大家猜到了深復(fù)制實(shí)現(xiàn)的方式:對(duì)屬性中所有引用類型的值,遍歷到是基本類型的值為止,從這種方式上,我們很容易就可以想到利用遞歸來實(shí)現(xiàn)深復(fù)制。
function deepCopy (obj) { var result; //引用類型分?jǐn)?shù)組和對(duì)象分別遞歸 if (Object.prototype.toString.call(obj) == "[object Array]") { result = [] for (i = 0; i < obj.length; i++) { result[i] = deepCopy(obj[i]) } } else if (Object.prototype.toString.call(obj) == "[object Object]") { result = {} for (var attr in obj) { result[attr] = deepCopy(obj[attr]) } } //值類型直接返回 else { return obj } return result }
上面的函數(shù)很簡單:對(duì)于傳入的參數(shù),首先判斷是否為引用類型,如果不是,直接返回即可;如果是,循環(huán)遍歷該對(duì)象的屬性,如果某個(gè)屬性還是引用類型,則針對(duì)該屬性再次調(diào)用deepCopy函數(shù),從而完成深復(fù)制。
附注對(duì)于淺復(fù)制,其實(shí)還有其他的實(shí)現(xiàn)方式,比如數(shù)組中concat和slice方法,對(duì)于這些還是希望大家自己了解,本本主要針對(duì)深淺復(fù)制的實(shí)現(xiàn)原理進(jìn)行解析。
小結(jié)對(duì)于深淺復(fù)制的區(qū)別,其實(shí)核心的關(guān)鍵點(diǎn)就是是只復(fù)制了第一屬性還是完全復(fù)制了所有的屬性,可能有些地方寫的稍顯啰嗦或者描述不當(dāng),歡迎提出意見和建議(然而我并不一定會(huì)聽,哈哈)。如果對(duì)讀者有幫助,還是希望能點(diǎn)個(gè)推薦。以上內(nèi)容屬于個(gè)人見解,如果有不同意見,歡迎指出和探討。請(qǐng)尊重作者的版權(quán),轉(zhuǎn)載請(qǐng)注明出處,如作商用,請(qǐng)與作者聯(lián)系,感謝!
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/82197.html
摘要:二這么分的好處就是在于節(jié)省內(nèi)存資源,便于合理回收內(nèi)存詳解中的深淺復(fù)制有了上面的鋪墊,那么我們理解起深淺復(fù)制就變得容易的許多。 前言 對(duì)于前端開發(fā)來說,我們經(jīng)常能夠遇到的問題就是js的深淺復(fù)制問題,通常情況下我們解決這個(gè)問題的方法就是用JSON.parse(JSON.Stringify(xx))轉(zhuǎn)換或者用類似于Inmmutable這種第三方庫來進(jìn)行深復(fù)制,但是我們還是要弄懂其中原理,這樣...
摘要:中有三種數(shù)據(jù)結(jié)構(gòu)棧堆隊(duì)列。前端進(jìn)擊的巨人一執(zhí)行上下文與執(zhí)行棧,變量對(duì)象中解釋執(zhí)行棧時(shí),舉了一個(gè)乒乓球盒子的例子,來演示棧的存取方式,這里再舉個(gè)栗子搭積木。對(duì)于基本類型,棧中存儲(chǔ)的就是它自身的值,所以新內(nèi)存空間存儲(chǔ)的也是一個(gè)值。 面試經(jīng)常遇到的深淺拷貝,事件輪詢,函數(shù)調(diào)用棧,閉包等容易出錯(cuò)的題目,究其原因,都是跟JavaScript基礎(chǔ)知識(shí)不牢固有關(guān),下層地基沒打好,上層就是豆腐渣工程,...
摘要:在之前的文章專題之?dāng)?shù)據(jù)類型和類型檢測中我有講過,中的數(shù)據(jù)類型分為兩種,基本數(shù)據(jù)類型和引用數(shù)據(jù)類型,基本數(shù)據(jù)類型是保存在棧的數(shù)據(jù)結(jié)構(gòu)中的是按值訪問,所以不存在深淺拷貝問題。 前言 在開發(fā)過程中,偶爾會(huì)遇到這種場景,拿到一個(gè)數(shù)據(jù)后,你打算對(duì)它進(jìn)行處理,但是你又希望拷貝一份副本出來,方便數(shù)據(jù)對(duì)比和以后恢復(fù)數(shù)據(jù)。 那么這就涉及到了 JS 中對(duì)數(shù)據(jù)的深淺拷貝問題,所謂深淺拷貝,淺拷貝的意思就是,...
摘要:深拷貝相比于淺拷貝速度較慢并且花銷較大。所以在賦值完成后,在棧內(nèi)存就有兩個(gè)指針指向堆內(nèi)存同一個(gè)數(shù)據(jù)。結(jié)果如下擴(kuò)展運(yùn)算符只能對(duì)一層進(jìn)行深拷貝如果拷貝的層數(shù)超過了一層的話,那么就會(huì)進(jìn)行淺拷貝那么我們可以看到和展開原算符對(duì)于深淺拷貝的結(jié)果是一樣。 JS中數(shù)據(jù)類型 基本數(shù)據(jù)類型: undefined、null、Boolean、Number、String和Symbol(ES6) 引用數(shù)據(jù)類型:...
摘要:總結(jié)綜上所述,數(shù)組的深拷貝比較簡單,方法沒有什么爭議,對(duì)象的深拷貝,比較好的方法是用的方法實(shí)現(xiàn),或者遞歸實(shí)現(xiàn),比較簡單的深復(fù)制可以使用實(shí)現(xiàn)參考資料知乎中的深拷貝和淺拷貝深入剖析的深復(fù)制 深淺復(fù)制對(duì)比 因?yàn)镴avaScript存儲(chǔ)對(duì)象都是存地址的,所以淺復(fù)制會(huì)導(dǎo)致 obj 和obj1 指向同一塊內(nèi)存地址。我的理解是,這有點(diǎn)類似數(shù)據(jù)雙向綁定,改變了其中一方的內(nèi)容,都是在原來的內(nèi)存基礎(chǔ)上做...
閱讀 1391·2023-04-25 16:45
閱讀 1929·2021-11-17 09:33
閱讀 2321·2021-09-27 14:04
閱讀 922·2019-08-30 15:44
閱讀 2642·2019-08-30 14:24
閱讀 3425·2019-08-30 13:59
閱讀 1699·2019-08-29 17:00
閱讀 899·2019-08-29 15:33