文章首發於sau交流學習社區
一、賦值(Copy)
賦值是將某一數值或對象賦給某個變量的過程,分為:
1、基本數據類型:賦值,賦值之后兩個變量互不影響
2、引用數據類型:賦**址**,兩個變量具有相同的引用,指向同一個對象,相互之間有影響
對基本類型進行賦值操作,兩個變量互不影響。
對引用類型進行賦**址**操作,兩個變量指向同一個對象,改變變量 a 之后會影響變量 b,哪怕改變的只是對象 a 中的基本類型數據。
通常在開發中並不希望改變變量 a 之后會影響到變量 b,這時就需要用到淺拷貝和深拷貝。
二、淺拷貝(Shallow Copy)
1、什么是淺拷貝
創建一個新對象,這個對象有着原始對象屬性值的一份精確拷貝。如果屬性是基本類型,拷貝的就是基本類型的值,如果屬性是引用類型,拷貝的就是內存地址 ,所以如果其中一個對象改變了這個地址,就會影響到另一個對象。
上圖中,`SourceObject` 是原對象,其中包含基本類型屬性 `field1` 和引用類型屬性 `refObj`。淺拷貝之后基本類型數據 `field2` 和 `filed1` 是不同屬性,互不影響。但引用類型 `refObj` 仍然是同一個,改變之后會對另一個對象產生影響。
簡單來說可以理解為淺拷貝只解決了第一層的問題,拷貝第一層的**基本類型值**,以及第一層的**引用類型地址**。
2、淺拷貝使用場景
2.1 Object.assign()
`Object.assign()` 方法用於將所有可枚舉屬性的值從一個或多個源對象復制到目標對象。它將返回目標對象。
有些文章說`Object.assign()` 是深拷貝,其實這是不正確的。
上面代碼改變對象 a 之后,對象 b 的基本屬性保持不變。但是當改變對象 a 中的對象 `book` 時,對象 b 相應的位置也發生了變化。
2.2 展開語法 `Spread`
2.3 Array.prototype.slice方法
slice不會改變原數組,`slice()` 方法返回一個新的數組對象,這一對象是一個由 `begin`和 `end`(不包括`end`)決定的原數組的**淺拷貝**。
可以看出,改變 `a[1]` 之后 `b[0]` 的值並沒有發生變化,但改變 `a[2][0]` 之后,相應的 `b[1][0]` 的值也發生變化。
說明 `slice()` 方法是淺拷貝,相應的還有`concat`等,在工作中面對復雜數組結構要額外注意。
三、深拷貝(Deep Copy)
3.1 什么是深拷貝?
深拷貝會拷貝所有的屬性,並拷貝屬性指向的動態分配的內存。當對象和它所引用的對象一起拷貝時即發生深拷貝。深拷貝相比於淺拷貝速度較慢並且花銷較大。拷貝前后兩個對象互不影響。
3.2 使用深拷貝的場景
3.2.1 JSON.parse(JSON.stringify(object))
完全改變變量 a 之后對 b 沒有任何影響,這就是深拷貝的魔力。
我們看下對數組深拷貝效果如何。
對數組深拷貝之后,改變原數組不會影響到拷貝之后的數組。
但是該方法有以下幾個問題:
(1)會忽略 `undefined`
(2)會忽略 `symbol`
(3)不能序列化函數
(4)不能解決循環引用的對象
(5)不能正確處理`new Date()`
(6)不能處理正則
其中(1)(2)(3) `undefined`、`symbol` 和函數這三種情況,會直接忽略。
其中(4)循環引用會報錯
其中(5)* `new Date` 情況下,轉換結果不正確。
解決方法轉成字符串或者時間戳就好了。
其中(6)正則情況下
PS:為什么會存在這些問題可以學習一下 JSON。
除了上面介紹的深拷貝方法,
常用的還有`jQuery.extend()` 和 `lodash.cloneDeep()`,后面文章會詳細介紹源碼實現。
四、總結
和原數據是否指向同一對象 | 第一層數據為基本數據類型 | 原數據中包含子對象 | |
賦值 | 是 | 改變會使原數據一起改變 | 改變會使原數據一起改變 |
淺拷貝 | 否 | 改變不會使原數據一起改變 | 改變會使原數據一起改變 |
深拷貝 | 否 | 改變不會使原數據一起改變 | 改變不會使原數據一起改變 |
五、參考
1、深拷貝和淺拷貝
3、MDN之展開語法