javascript
通俗易懂讲解JavaScript深拷贝和浅拷贝
基本類型和引用類型
在開始講解JavaScript的深拷貝和淺拷貝之前,先要認識JavaScript的兩種基本數(shù)據(jù)類型。一種是基本類型(值類型),另外一種是引用類型。其中基本類型包括undefined、null、number、string和boolean,這幾種類型在內(nèi)存中都有固定的大小和空間。引用類型包括object,這種值的大小不固定,可以動態(tài)添加屬性和方法,而基本類型則不可以。
基本類型的值保存在內(nèi)存中的棧中,而引用數(shù)據(jù)類型的值保存在內(nèi)存中的堆中,在棧內(nèi)存中保存著指向堆內(nèi)存的指針。如果此時你對棧或者堆這樣的數(shù)據(jù)結(jié)構(gòu)不太了解的話,沒有關(guān)系,不會影響你對后面內(nèi)容的理解。只要先記住棧和堆著兩個名詞就可以。
如圖所示,這是一個引用數(shù)據(jù)類型,也就是對象(object)在內(nèi)存中的保存方式。比如我們定義方式如下所示:
對象的內(nèi)容保存在堆中,而對象的引用,也就是這里的Foo,保存在棧中,并指向?qū)?yīng)的堆。
我們明白了這樣的道理之后,就會清楚對于基本數(shù)據(jù)類型的值的復(fù)制,是對棧內(nèi)存中的值直接進行復(fù)制,所以復(fù)制的就是值本身,相當(dāng)于復(fù)制了一個副本。會在棧中開辟一塊全新的內(nèi)存。所以修改一個變量的值不會影響另外一個變量的值。所以對于基本數(shù)據(jù)類型而言,沒有淺拷貝和深拷貝之分,或者說直接就是深拷貝。
引用類型淺拷貝
對于引用類型進行復(fù)制,如下代碼所示,復(fù)制的并不是堆內(nèi)存中的數(shù)據(jù),而是指向堆中的棧內(nèi)存的指針。如上面那個圖所示,原先在標(biāo)記為a的棧內(nèi)存中有一個Foo變量,指向?qū)?yīng)的堆內(nèi)存中的數(shù)據(jù),現(xiàn)在通過復(fù)制這個指針,在堆內(nèi)存中新開辟一個內(nèi)存空間,我們標(biāo)記為c,其指向的和a是同樣的堆內(nèi)存數(shù)據(jù)。所以修改a和c都會導(dǎo)致堆內(nèi)存中的數(shù)據(jù)發(fā)生改變。
let Foo = {a: 3,b: 4 }let newFoo = FoonewFoo.a = 5之后輸出的結(jié)果是:
由于Foo和NewFoo都指向同樣的對象,所以改變NewFoo中的數(shù)據(jù),Foo中的數(shù)據(jù)同樣也會發(fā)生改變。那如何去改變這一點呢?讓Foo和NewFoo中的數(shù)據(jù)不同,改變一個不會影響另外一個?我們先談一下淺拷貝的解決辦法。
淺拷貝可以使用Object.assign()來實現(xiàn)。我們還是舉上面的例子:
let Foo = {a: 3,b: 4 }// let newFoo = Foo Object.assign(newFoo, Foo)newFoo.a = 5這時候輸出的結(jié)果是:
可以看出,Foo的結(jié)果并沒有發(fā)生改變,達到了拷貝的目的。但這為什么又稱之為淺拷貝呢?我們來看下面一個例子。
這時候輸出結(jié)果是:
可以看出,此時c的結(jié)果發(fā)生了改變,已經(jīng)變成2了。這就是淺拷貝的意思。只拷貝了最淺層的對象,當(dāng)對象里面嵌套的對象發(fā)生改變時,其內(nèi)部值也會發(fā)生改變,沒有達到完全隔離的效果,只實現(xiàn)了淺層的拷貝。下面我們一起來看一下如何實現(xiàn)深層拷貝。
引用類型深拷貝
實現(xiàn)深層拷貝方式有兩種,一種是通過JSON的方式,另外一種是通過類型判斷和遞歸的方式。我們分別來實現(xiàn)一下。
- JSON方式
JSON有兩個方法JSON.parse()和JSON.stringify(),前者可以將JSON字符串轉(zhuǎn)換成JavaScript對象,后者可以將JavaScript對象轉(zhuǎn)換成JSON字符串。
所以解決思路就是先將引用類型數(shù)據(jù)變成JSON,再將其從JSON變回來,這樣經(jīng)過這樣一層轉(zhuǎn)換,就會要求計算機從內(nèi)存中重新開辟一塊新的內(nèi)存空間。Foo和newFoo之間就不會發(fā)生任何聯(lián)系了。我們通過代碼來去具體看一下。
let Foo = {a: {c:1},b: 4 }let str = JSON.stringify(Foo) let newFoo = JSON.parse(str)newFoo.a.c = 5此時Foo輸出的結(jié)果是:
可以看到c并沒有發(fā)生改變,實現(xiàn)了深拷貝。
- 通過遞歸的方式實現(xiàn)
之前JSON方式實現(xiàn)深拷貝就是靠著返回一個獨立的對象來實現(xiàn)深拷貝,受此啟發(fā),如果要是我們自己來實現(xiàn)深拷貝的話,我們可以將原來的數(shù)據(jù)完全拷貝之后,然后返回一個獨立的新對象,這樣的話,也可以實現(xiàn)深拷貝。
如果要實現(xiàn)完全遍歷的話,就要考慮Object的不同具體數(shù)據(jù)類型了,雖然{}和[]都是Object類型,但顯然生成的對象是不一樣的,當(dāng)然,可能Object還可能有別的數(shù)據(jù)類型的樣子,這里我們就以這兩個為例,來去自己動手實現(xiàn)深拷貝。
那么首先需要寫一個數(shù)據(jù)類型判斷的函數(shù):
let checkType = data => {return Object.prototype.toString.call(data).slice(8, -1) }這里我們使用Object的toString方法來去判斷data的數(shù)據(jù)類型,后面的slice是返回值結(jié)果進行一定的截取,保留我們想要的結(jié)果。
下面定義深拷貝函數(shù):
let deepClone = target => {let targetType = checkType(target)let result// 初始化操作if (targetType === 'Object') {result = {}} else if (targetType === 'Array') {result = []} else {// 都不是的話證明是基本數(shù)據(jù)類型,基本數(shù)據(jù)// 類型只會有一個值,所以直接返回這個值就可以了return target}// target不是基本類型,進入遍歷for (let i in target) {let value = target[i]let valueType = checkType(value)if (valueType === 'Object' || valueType === 'Array') {result[i] = deepClone(value) // 遞歸} else {// 是基本類型直接賦值result[i] = value}}return result }這樣我們實現(xiàn)了自己定義的深拷貝。
總結(jié)
以上是生活随笔為你收集整理的通俗易懂讲解JavaScript深拷贝和浅拷贝的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 一劳永逸解决npm安装速度慢的问题
- 下一篇: 详解JavaScript中ES5和ES6