摘要:引用類型值指的是那些保存在堆內(nèi)存中的對(duì)象,所以引用類型的值保存的是一個(gè)指針,這個(gè)指針指向存儲(chǔ)在堆中的一個(gè)對(duì)象。因此當(dāng)操作結(jié)束后,這兩個(gè)變量實(shí)際上指向的是同一個(gè)在堆內(nèi)存中的對(duì)象,改變其中任意一個(gè)對(duì)象,另一個(gè)對(duì)象也會(huì)跟著改變。
一、為什么有深拷貝和淺拷貝?
???? 這個(gè)要從js中的數(shù)據(jù)類型說起,js中數(shù)據(jù)類型分為基本數(shù)據(jù)類型和引用數(shù)據(jù)類型。
????基本類型值指的是那些保存在棧內(nèi)存中的簡(jiǎn)單數(shù)據(jù)段,即這種值是完全保存在內(nèi)存中的一個(gè)位置。包含Number,String,Boolean,Null,Undefined ,Symbol。
????引用類型值指的是那些保存在堆內(nèi)存中的對(duì)象,所以引用類型的值保存的是一個(gè)指針,這個(gè)指針指向存儲(chǔ)在堆中的一個(gè)對(duì)象。除了上面的 6 種基本數(shù)據(jù)類型外,剩下的就是引用類型了,統(tǒng)稱為 Object 類型。細(xì)分的話,有:Object 類型、Array 類型、Date 類型、RegExp 類型、Function 類型 等。
????正因?yàn)橐妙愋偷倪@種機(jī)制, 當(dāng)我們從一個(gè)變量向另一個(gè)變量復(fù)制引用類型的值時(shí),實(shí)際上是將這個(gè)引用類型在棧內(nèi)存中的引用地址復(fù)制了一份給新的變量,其實(shí)就是一個(gè)指針。因此當(dāng)操作結(jié)束后,這兩個(gè)變量實(shí)際上指向的是同一個(gè)在堆內(nèi)存中的對(duì)象,改變其中任意一個(gè)對(duì)象,另一個(gè)對(duì)象也會(huì)跟著改變。
????因此深拷貝和淺拷貝只發(fā)生在引用類型中。簡(jiǎn)單來說他們的區(qū)別在于:
1. 層次淺拷貝 只會(huì)將對(duì)象的各個(gè)屬性進(jìn)行依次復(fù)制,并不會(huì)進(jìn)行遞歸復(fù)制,也就是說只會(huì)賦值目標(biāo)對(duì)象的第一層屬性。
深拷貝不同于淺拷貝,它不只拷貝目標(biāo)對(duì)象的第一層屬性,而是遞歸拷貝目標(biāo)對(duì)象的所有屬性。
2. 是否開辟新的棧淺拷貝 對(duì)于目標(biāo)對(duì)象第一層為基本數(shù)據(jù)類型的數(shù)據(jù),就是直接賦值,即「?jìng)髦怠?;而?duì)于目標(biāo)對(duì)象第一層為引用數(shù)據(jù)類型的數(shù)據(jù),就是直接賦存于棧內(nèi)存中的堆內(nèi)存地址,即「?jìng)髦贰?并沒有開辟新的棧,也就是復(fù)制的結(jié)果是兩個(gè)對(duì)象指向同一個(gè)地址,修改其中一個(gè)對(duì)象的屬性,則另一個(gè)對(duì)象的屬性也會(huì)改變,
深拷貝 而深復(fù)制則是開辟新的棧,兩個(gè)對(duì)象對(duì)應(yīng)兩個(gè)不同的地址,修改一個(gè)對(duì)象的屬性,不會(huì)改變另一個(gè)對(duì)象的屬性。
二、淺拷貝以下是實(shí)現(xiàn)淺拷貝的幾種實(shí)現(xiàn)方式:
1.Array.concat()const arr = [1,2,3,4,[5,6]]; const copy = arr.concat(); 利用concat()創(chuàng)建arr的副本 改變基本類型值,不會(huì)改變?cè)瓟?shù)組 copy[0] = 2; arr; //[1,2,3,4,[5,6]]; 改變數(shù)組中的引用類型值,原數(shù)組也會(huì)跟著改變 copy[4][1] = 7; arr; //[1,2,3,4,[5,7]];
能實(shí)現(xiàn)類似效果的還有slice()和Array.from()等,大家可以自己嘗試一下~
2.Object.assign()const obj1 = {x: 1, y: 2}; const obj2 = Object.assign({}, obj1); obj2.x = 2; 修改obj2.x,改變對(duì)象中的基本類型值 console.log(obj1) //{x: 1, y: 2} //原對(duì)象未改變 console.log(obj2) //{x: 2, y: 2}
const obj1 = { x: 1, y: { m: 1 } }; const obj2 = Object.assign({}, obj1); obj2.y.m = 2; 修改obj2.y.m,改變對(duì)象中的引用類型值 console.log(obj1) //{x: 1, y: {m: 2}} 原對(duì)象也被改變 console.log(obj2) //{x: 2, y: {m: 2}}三、深拷貝 1.JSON.parse()和JSON.stringify()
const obj1 = { x: 1, y: { m: 1 } }; const 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}} 原對(duì)象未改變 console.log(obj2) //{x: 2, y: {m: 2}}
這種方法使用較為簡(jiǎn)單,可以滿足基本日常的深拷貝需求,而且能夠處理JSON格式能表示的所有數(shù)據(jù)類型,但是有以下幾個(gè)缺點(diǎn):
undefined、任意的函數(shù)、正則表達(dá)式類型以及 symbol 值,在序列化過程中會(huì)被忽略(出現(xiàn)在非數(shù)組對(duì)象的屬性值中時(shí))或者被轉(zhuǎn)換成 null(出現(xiàn)在數(shù)組中時(shí));
它會(huì)拋棄對(duì)象的constructor。也就是深拷貝之后,不管這個(gè)對(duì)象原來的構(gòu)造函數(shù)是什么,在深拷貝之后都會(huì)變成Object;
如果對(duì)象中存在循環(huán)引用的情況無法正確處理。
2.遞歸function deepCopy1(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; } const obj1 = { x: { m: 1 }, y: undefined, z: function add(z1, z2) { return z1 + z2 }, a: Symbol("foo") }; const obj2 = deepCopy1(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)}四、循環(huán)引用
看似遞歸已經(jīng)完全解決我們的問題了,然而還有一種情況我們沒考慮到,那就是循環(huán)引用
1.父級(jí)引用這里的父級(jí)引用指的是,當(dāng)對(duì)象的某個(gè)屬性,正是這個(gè)對(duì)象本身,此時(shí)我們?nèi)绻M(jìn)行深拷貝,可能會(huì)在子元素->父對(duì)象->子元素...這個(gè)循環(huán)中一直進(jìn)行,導(dǎo)致棧溢出。比如下面這個(gè)例子:
const obj1 = { x: 1, y: 2 }; obj1.z = obj1; const obj2 = deepCopy1(obj1); 棧溢出
解決辦法是:只需要判斷一個(gè)對(duì)象的字段是否引用了這個(gè)對(duì)象或這個(gè)對(duì)象的任意父級(jí)即可,可以修改上面的deepCopy1函數(shù):
function deepCopy2(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.originParent === obj) { //循環(huán)引用返回同級(jí)的新對(duì)象 return _parent.currentParent; } _parent = _parent.parent } for(let i=0,len=keys.length;i2. 同級(jí)引用 假設(shè)對(duì)象obj有a,b,c三個(gè)子對(duì)象,其中子對(duì)象c中有個(gè)屬性d引用了對(duì)象obj下面的子對(duì)象a。
const obj= { a: { name: "a" }, b: { name: "b" }, c: { } }; c.d.e = obj.a;此時(shí)c.d.e和obj.a 是相等的,因?yàn)樗鼈円玫氖峭粋€(gè)對(duì)象
console.log(c.d.e === obj.a); //true如果我們調(diào)用上面的deepCopy2函數(shù)
const copy = deepCopy2(obj); console.log(copy.a); // 輸出: {name: "a"} console.log(copy.d.e);// 輸出: {name: "a"} console.log(copy.a === copy.d.e); // 輸出: false以上表現(xiàn)我們就可以看出,雖然opy.a 和copy.d.e在字面意義上是相等的,但二者并不是引用的同一個(gè)對(duì)象,這點(diǎn)上來看對(duì)象copy和原對(duì)象obj還是有差異的。
這種情況是因?yàn)閛bj.a并不在obj.d.e的父級(jí)對(duì)象鏈上,所以deepCopy2函數(shù)就無法檢測(cè)到obj.d.e對(duì)obj.a也是一種引用關(guān)系,所以deepCopy2函數(shù)就將obj.a深拷貝的結(jié)果賦值給了copy.d.e。
解決方案:父級(jí)的引用是一種引用,非父級(jí)的引用也是一種引用,那么只要記錄下對(duì)象A中的所有對(duì)象,并與新創(chuàng)建的對(duì)象一一對(duì)應(yīng)即可。
function deepCopy3(obj) { // hash表,記錄所有的對(duì)象的引用關(guān)系 let map = new WeakMap(); function dp(obj) { let result = null; let keys = Object.keys(obj); let key = null, temp = null, existobj = null; existobj = map.get(obj); //如果這個(gè)對(duì)象已經(jīng)被記錄則直接返回 if(existobj) { return existobj; } result = {} map.set(obj, result); for(let i =0,len=keys.length;i五、總結(jié) ????其實(shí)拷貝的方式還有很多種,比如jquery中的$.extend,lodash的_.cloneDeep等等,關(guān)于拷貝中還有很多問題值得深究,比如正則類型的值如何拷貝,原型上的屬性如何拷貝,這些我都會(huì)慢慢研究噠!大家也可以思考一下~
????最后,歡迎點(diǎn)贊和收藏??!錯(cuò)誤之處歡迎指正(`?ω?′)
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/95288.html
摘要:案例中的賦值就是典型的淺拷貝,并且深拷貝與淺拷貝的概念只存在于引用類型。修改修改經(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 num...
摘要:深淺拷貝簡(jiǎn)介中對(duì)于和這兩個(gè)類型,把一個(gè)變量賦值給另一個(gè)變量淺拷貝只是對(duì)拷貝對(duì)象的引用,深拷貝是徹底拷貝,生成一個(gè)新的屬性相同的對(duì)象淺拷貝淺拷貝只是對(duì)拷貝對(duì)的引用,兩者相互影響淺拷貝的實(shí)現(xiàn)簡(jiǎn)單賦值實(shí)現(xiàn)例子拷貝了,改變,也會(huì)改變,改變之后者還是 深淺拷貝簡(jiǎn)介 javascript中對(duì)于Object和Array這兩個(gè)類型,把一個(gè)變量賦值給另一個(gè)變量;淺拷貝只是對(duì)拷貝對(duì)象的引用,深拷貝是徹底拷...
摘要:一篇文章徹底說清的深拷貝淺拷貝這篇文章的受眾第一類業(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ǔ),將來好去面試官前秀操作...
閱讀 2633·2021-11-19 09:59
閱讀 2126·2019-08-30 15:55
閱讀 1008·2019-08-29 13:30
閱讀 1408·2019-08-26 10:18
閱讀 3151·2019-08-23 18:36
閱讀 2451·2019-08-23 18:25
閱讀 1231·2019-08-23 18:07
閱讀 499·2019-08-23 17:15