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