js深拷貝
-
- 什麼是淺拷貝?
- 什麼是深拷貝?
- 深拷貝的實作
-
- 最簡單的方式
- 基礎方式
- 基礎方式修複
- 解決引用自身問題
什麼是淺拷貝?
新的對象複制已有對象中非對象屬性的值和對象屬性的引用就是淺拷貝。
不了解? 下面用 Object.assign 展現淺拷貝
var obj = {x: 1,y: 2,a: {z: 3}}
var obj1 = Object.assign({}, obj)
obj.a.z = 4
obj.x = 2
console.log(obj) // {x: 2, y: 2, a: {z: 4}
console.log(obj1) // {x: 1, y: 2, a: {z: 4}}
從輸出結果可以看出,淺拷貝時對非對象是會有新的記憶體位址的,而對對象的拷貝隻是對已存在對象的屬性引用,是以當原對象改變時,淺拷貝對象也會改變
什麼是深拷貝?
深拷貝會另外拷貝一份一個一模一樣的對象,從堆記憶體中開辟一個新的區域存放新對象,新對象跟原對象不共享記憶體,修改新對象不會改到原對象。
同樣的,我們使用 JSON.parse(JSON.stringify()) 來說明深拷貝
var obj = {x: 1,y: 2,a: {z: 3}}
var obj1 = JSON.parse(JSON.stringify(obj))
obj.x = 2
obj.a.z = 4
console.log(obj) // {x: 2, y: 2, a: {z: 4}}
console.log(obj1) // {x: 1, y: 2, a: {z: 3}}
由上面代碼可以看出,深拷貝時,不管是對象還是非對象,都是完全的複制,而不是引用,是拷貝出了一個全新的對象,和原來對象并無關系
深拷貝的實作
最簡單的方式
最簡單的深拷貝實作方式,即為我們前面使用的方式 ---- JSON.parse(JSON.stringify())
function clone(target){
return JSON.parse(JSON.stringify(target))
}
這個方法雖然是最簡單的,但卻是最實用的,因為大部分的場景都能用這條簡單的語句解決。但顯然,既然不能解決所有問題,那它就是不完善的。
基礎方式
function clone1(target) {
if(typeof target === 'object') {
let cloneTarget = {}
for(const key in target){
cloneTarget[key] = clone1(target[key])
}
return cloneTarget
}else {
return target
}
}
這裡我們使用遞歸來處理深拷貝,如果是原始類型,直接傳回;如果是引用類型,建立一個新對象,将對象内的屬性深拷貝至新對象中,一直到最終的對象中為原始類型為止
基礎方式修複
之前的基礎方式有一個問題----我們并沒有處理數組對象,如果對象是數組呢?
function newClone1(target) {
if(typeof target === 'object') {
let cloneTarget = Array.isArray(target) ? [] : {}
for(const key in target){
cloneTarget[key] = newClone1(target[key])
}
return cloneTarget
}else {
return target
}
}
其實處理方式非常簡單,隻要在建立新對象時判斷對象是否為數組。是,則建立數組對象
解決引用自身問題
如果使用之前的基礎方式,其實是有問題的,如下:
var target = {a: 1, b: {c: 2}, c: [1, 2, 3, 4, 5]}
target .d = target
let cloneTarget = newClone1(target)
// 報錯資訊如下
/*
Uncaught RangeError: Maximum call stack size exceeded
at newClone1 (<anonymous>:1:19)
at newClone1 (<anonymous>:5:26)
at newClone1 (<anonymous>:5:26)
at newClone1 (<anonymous>:5:26)
at newClone1 (<anonymous>:5:26)
at newClone1 (<anonymous>:5:26)
at newClone1 (<anonymous>:5:26)
at newClone1 (<anonymous>:5:26)
at newClone1 (<anonymous>:5:26)
at newClone1 (<anonymous>:5:26)
*/
這是因為循環引用,遞歸永遠出不來導緻的棧溢出,那麼,我們如何解決這個問題呢?
其實也比較簡單,我們需要一個新的資料類型—Map
在 JavaScript 的對象,本質上是鍵值對的集合,但傳統上隻能使用字元串當做建,這就對它的使用産生了限制。
在ES6中,提供了 Map 資料結構。它也是鍵值對的集合,但是‘鍵’的範圍不僅是字元串,Map提供的是‘值-值’。
并且,Map 有對應的 WeakMap,可以自動進行回收記憶體
考慮這種特性,我們可以對之前的拷貝進行完善
如果想詳細了解 Map ,可以前往 http://es6.ruanyifeng.com/#docs/set-map
function clone2(target, map = new WeakMap()) {
if(typeof target === 'object') {
let cloneTarget = Array.isArray(target) ? [] : {}
if(map.get(target)){
return map.get(target)
}
map.set(target, cloneTarget)
for(const key in target){
cloneTarget[key] = clone2(target[key], map)
}
return cloneTarget
}else {
return target
}
}
到這裡,我們基本上算是解決了絕大部分的對象深拷貝。既然是絕大部分,那麼說明還是存在一些問題
如果你還不滿足于這樣的深拷貝
推薦你去看這篇文章: https://juejin.im/post/5d6aa4f96fb9a06b112ad5b1
ps: 我的這篇文章基本是學習這位大牛