深拷贝的缺点_拷贝?还傻傻分不清深浅?
「引言」
?臣聞求木之長者,必固其根本;欲流之遠(yuǎn)者,必浚其泉源。
??????????????????????????????????????---- 魏征 《諫太宗十思疏》
?或許你會問到,網(wǎng)上已經(jīng)把深淺拷貝(算一個面試的高頻考點(diǎn)了吧)的文章都快寫爛了,為什么自己還要重新操刀寫一遍呢!?
?首先,一些文章,講不清也道不明本質(zhì);另外,確實(shí)有很優(yōu)秀的人寫的很是生動,讓我直接看到了風(fēng)景,卻不知道沿途是不是也有自己錯過的美景,唯有嘗試過,才會真正成為自己的~
?首先,我們先來看一張筆者整理的腦圖,梳理一下~
希望通過本文的總結(jié),你會有以下幾點(diǎn)收獲:
- 什么是深淺拷貝?他們與賦值有何區(qū)別?
- 淺拷貝的實(shí)現(xiàn)方式有哪些?
- 深拷貝的實(shí)現(xiàn)方式有哪些?
本章節(jié)直接從拷貝開始說起,對于基本數(shù)據(jù)類型,引用數(shù)據(jù)類型之前的區(qū)別,可以看看上面的思維導(dǎo)圖
引用數(shù)據(jù)類型拷貝
我們從以下三個方面來看看這塊的內(nèi)容
- 賦值
- 淺拷貝
- 深拷貝
賦值
引用類型的賦值是傳址。其引用指向堆中的同一個對象,因此操作其中一個對象,另一個對象是會跟著一起變的。
舉個栗子:
let?lucy?=?{????name:?'lucy',
????age:?23
}
let?lilei?=?lucy
lilei.name?=?'lilei'
lilei.age?=?24
console.log('lucy',?lucy)??//?lucy?{name:?"lilei",?age:?24}
console.log('lilei',?lilei)?//?lilei?{name:?"lilei",?age:?24}
上面栗子中可以看出來,修改了 lilei 的數(shù)據(jù),lucy也會跟著變。這是初學(xué)者(筆者也曾這樣)經(jīng)常犯的一個錯,后來深刻理解了對象內(nèi)存的重要性!改掉了這個惡習(xí)~
那么我們該如何不讓彼此之間不影響呢?
接下來我們引出了 拷貝這個概念,拷貝又分深拷貝和淺拷貝。
來看一看具體是什么和相關(guān)區(qū)別吧。
「注意:」
「本質(zhì)&使用場景」:都是復(fù)雜對象,就是說對象的屬性還是對象
淺拷貝
「本質(zhì)」:只復(fù)制一層對象,當(dāng)對象的屬性是引用類型時,實(shí)質(zhì)復(fù)制的是其引用,當(dāng)引用值指向發(fā)生改變時也會跟著改變
「原理」:遍歷并復(fù)制,最后返回一個對象
來動手實(shí)現(xiàn)一個簡單的淺拷貝吧
//?實(shí)現(xiàn)淺拷貝?for??in?let?shallowCopy?=?(obj)?=>?{
????let?rst?=?{}
????for?(let?key?in?obj)?{
????????//?只復(fù)制本身擁有的屬性(非繼承過來的屬性)
????????if?(obj.hasOwnProperty(key))?{
????????????rst[key]?=?obj[key]
????????}
????}
????return?rst
}
let?lucy?=?{
????name:?'lucy',
????age:?23,
????hobby:?['running',?'swimming']
}
let?lilei?=?shallowCopy(lucy)
lilei.name?=?'lilei'
lilei.age?=?24
lilei.hobby[0]?=?'reading'
console.log('lucy',?lucy)
//?lucy?{name:?"lucy",?age:?23,?hobby:?['reading',?'swimming']}
console.log('lilei',?lilei)
//?lilei?{name:?"lilei",?age:?24,?hobby:?['reading',?'swimming']}
我們可以看到,當(dāng)對象的屬性是引用類型時,實(shí)質(zhì)復(fù)制的是其引用,當(dāng)引用值指向發(fā)生改變時也會跟著改變。
深拷貝
「實(shí)質(zhì)」:深拷貝出來的對象會互不影響
「原理」:對對象中子對象進(jìn)行遞歸拷貝
我們下面會手寫一個深拷貝哈~接著往下看,會有不一樣的收貨!
淺拷貝的實(shí)現(xiàn)方式
平常用到的淺拷貝有以下幾種(歡迎評論補(bǔ)充,互相分享進(jìn)步)
- Object.assign()
- 擴(kuò)展運(yùn)算符(...)
- Array.prototype.slice()
Object.assign()
首先 Object.assign(target, source)可以把n個源對象拷貝到目標(biāo)對象中去(這不是本節(jié)重點(diǎn)討論的內(nèi)容,先一筆帶過)
然后呢,Object.assign 是 ES6新增的對象方法,那么它到底是一個深拷貝還是一個淺拷貝的方法呢?
告訴你一個絕招吧(小點(diǎn)聲)!
「拷貝對象時,第一級屬性是深拷貝,以后級別淺拷貝」
舉個栗子你就知道了
let?lucy?=?{????name:?'lucy',
????age:?23,
????hobby:?['running',?'swimming']
}
let?lilei?=?Object.assign({},?lucy)
lilei.name?=?'lilei'
lilei.age?=?24
lilei.hobby[0]?=?'reading'
console.log('lucy',?lucy)
//?lucy?{name:?"lucy",?age:?23,?hobby:?['reading',?'swimming']}
console.log('lilei',?lilei)
//?lilei?{name:?"lilei",?age:?24,?hobby:?['reading',?'swimming']}
可以看出這個和咱們上面實(shí)現(xiàn)的那個淺拷貝的結(jié)果是一樣的。
還是那句話:「拷貝對象時,第一級屬性是深拷貝,以后級別淺拷貝」
是不是簡簡單單呢~
擴(kuò)展運(yùn)算符(...)
這個和 Object.assign 一樣,我們來看個栗子驗(yàn)證一下
let?lucy?=?{????name:?'lucy',
????age:?23,
????hobby:?['running',?'swimming']
}
let?lilei?=?{...lucy}
lilei.name?=?'lilei'
lilei.age?=?24
lilei.hobby[0]?=?'reading'
console.log('lucy',?lucy)
//?lucy?{name:?"lucy",?age:?23,?hobby:?['reading',?'swimming']}
console.log('lilei',?lilei)
//?lilei?{name:?"lilei",?age:?24,?hobby:?['reading',?'swimming']}
哦~一毛一樣啊和上面。
Array.prototype.slice()
說到這個方法,我第一次看見的時候是在看 vue 源碼的時候,那個時候真是漲見識(姿勢)了
話不多說,看一下就知道
//?Dep?notify?方法Dep.prototype.notify?=?function?notify()?{
????var?subs?=?this.subs.slice()
????//?...
}
利用了slice() 方法會返回一個新的數(shù)組對象,但也是一個淺拷貝的方法。
即「拷貝對象時,第一級屬性是深拷貝,以后級別淺拷貝」
看一個具體的栗子
let?a1?=?[1,?2,?[3,?4]]let?a2?=?a1.slice()
a2[1]?=?3
a2[2][0]?=?5
console.log('a1',?a1)?//?a1?(3)?[1,?2,?[5,?4]]
console.log('a2',?a2)?//?a2?(3)?[1,?3,?[5,?4]]
是不是驗(yàn)證了這個道理呢~
同時也要去「注意」 concat這些會返回一個新的數(shù)組對象方法等,避免造成一些工作開發(fā)者不必要的困擾~
深拷貝的實(shí)現(xiàn)方式
深拷貝拷貝出來的對象互不影響,但深拷貝相比于淺拷貝速度會比較慢且開銷會較大,所以考慮清楚數(shù)據(jù)結(jié)構(gòu)有幾層,不是很復(fù)雜的數(shù)據(jù)結(jié)構(gòu)建議淺拷貝來節(jié)省性能。
看一種最簡單的深拷貝實(shí)現(xiàn)方式
JSON.parse(JSON.stringify())
**原理:**能將json的值json化
就是指純JSON數(shù)據(jù),不包含循環(huán)引用,循環(huán)引用會報(bào)錯
拿之前的栗子改造一下看看有哪些需要注意的地方
let?lucy?=?{????name:?'lucy',
????age:?23,
????hobby:?['running',?'swimming'],
????say:?function()?{
????????return?this.name
????},
????other:?undefined
}
let?lilei?=?JSON.parse(JSON.stringify(lucy))
lilei.name?=?'lilei'
lilei.age?=?24
lilei.hobby[0]?=?'reading'
console.log('lucy',?lucy)
//?lucy?{
//????name:?'lucy',
//????age:?23,
//????hobby:?['running',?'swimming'],
//????say:?function()?{
//????????return?this.name
//?????},
//????other:?undefined
//???}
console.log('lilei',?lilei)
//?lilei?{age:?24,?hobby:?['reading',?swimming],?name:?'lilei'}
可以看出來這個方法還是挺強(qiáng)大的。
但是也能發(fā)現(xiàn)一些問題
- 會忽略 undefined ?Symbol
- 不能序列化函數(shù)
- 不能解決循環(huán)引用的對象
- 不能處理正則
- 不能正確處理 new Date() (轉(zhuǎn)換成時間戳可以拷貝)
此外,深拷貝的其他方法還有 jQuery.extend()以及一些三方庫實(shí)現(xiàn)的深拷貝 lodash.cloneDeep()等等。大家感興趣可自行了解,繼續(xù)深造~
重頭戲,面試常考,手寫一個深拷貝,哈哈哈是不是就等這個呢~
我們改造一下上面的淺拷貝
遞歸實(shí)現(xiàn)深拷貝
//?判斷邊界,?null?這個特殊情況let?isObject?=?obj?=>?typeof?obj?===?'object'?&&?obj?!==?null
//?遞歸實(shí)現(xiàn)深拷貝
let?deepClone?=?(obj)?=>?{
????//?先判斷是數(shù)組還是對象
????let?newObj?=?Array.isArray(obj)???[]?:?{}
????if?(isObject(obj))?{
????????for?(let?key?in?obj)?{
????????????if?(obj.hasOwnProperty(key))?{
????????????????if?(isObject(obj[key]))?{
????????????????????//?遞歸調(diào)用每一層
????????????????????newObj[key]?=?deepClone(obj[key])
????????????????}?else?{
????????????????????newObj[key]?=?obj[key]
????????????????}
????????????}
????????}
????}
????return?newObj
}
let?aa?=?{
????name:?'aa',
????car:?['寶馬',?'奔馳'],
????driver:?function?()?{?},
????age:?undefined
}
let?bb?=?deepClone(aa)?//?全部拷貝了一份
bb.name?=?'bb'
bb.age?=?20
bb.driver?=?'xxx'
console.log(bb)?
//?{?name:?'bb',?car:?[?'寶馬',?'奔馳'?],?driver:?'xxx',?age:?20?}
console.log(aa)
//?{?name:?'aa',?car:?[?'寶馬',?'奔馳'?],?driver:?function()?{},?age:?undefined?}
可以看出來,咱們這個遞歸實(shí)現(xiàn)的深拷貝,規(guī)避掉了 上面 JSON.parse(JSON.stringify())的一些弊端。但是還存在一些問題
哈希表
針對于循環(huán)檢測,我們可以使用哈希檢測的方法,比如設(shè)置一個數(shù)組或者是已經(jīng)拷貝的對象,當(dāng)檢測到對象已經(jīng)存在哈希表時,就去除該值。
let?isObject?=?obj?=>?typeof?obj?===?'object'?&&?obj?!==?null;let?deepClone?=?(source,?hash?=?new?WeakMap())?=>?{
????if?(!isObject(source))?return?source?//?非對象返回自身
????if?(hash.has(source))?return?hash.get(source)?//?新增檢測,?查哈希表
????let?target?=?Array.isArray(source)???[]?:?{}
????hash.set(source,?target)?//?設(shè)置哈希表值
????for?(let?key?in?source)?{
????????if?(Object.prototype.hasOwnProperty.call(source,?key))?{
????????????target[key]?=?isObject(source[key])???deepClone(source[key],?hash)?:?source[key];?//?傳入哈希表
????????}
????}
????return?target
}
let?obj?=?{
????a:?1,
????b:?{
????????c:?2,
????????d:?3
????}
}
obj.a?=?obj.b;
obj.b.c?=?obj.a;
let?clone_obj?=?deepClone(obj)
console.log(clone_obj)
上面實(shí)現(xiàn)有點(diǎn)難度,如果未能一下看透,不妨先跳過,完成之前的那個深拷貝就夠了,當(dāng)然,我喜歡不懼困難的人~
剩下的兩個就交給喜歡深度思考的人來去頭腦風(fēng)暴一下吧。
最后總結(jié)一下
| 賦值 | 是 | 改變會影響原數(shù)據(jù) | 改變會影響原數(shù)據(jù) |
| 淺拷貝 | 否 | 改變「不會」影響原數(shù)據(jù) | 改變會影響原數(shù)據(jù) |
| 深拷貝 | 是 | 改變「不會」影響原數(shù)據(jù) | 改變「不會」影響原數(shù)據(jù) |
寫在最后
?享受過程帶來的喜悅,學(xué)會去克服自己的缺點(diǎn)!
?總結(jié)
以上是生活随笔為你收集整理的深拷贝的缺点_拷贝?还傻傻分不清深浅?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 速卖通手机端怎样加入html,如何正确使
- 下一篇: mysql报错级别_MySQL启动出现几