JavaScript的深淺複製 為什麼有深複製、淺複製? JavaScript中有兩種數據類型,基本數據類型如 、`null boolean number string Object`。簡單數據類型只存儲在記憶體中的 棧區 ,複製的時候是值傳遞給新的索引。而複雜數據類型由棧區和 堆區 共同儲存,棧區執 ...
JavaScript的深淺複製
為什麼有深複製、淺複製?
JavaScript中有兩種數據類型,基本數據類型如undefined
、null
、boolean
、number
、string
,另一類是Object
。簡單數據類型只存儲在記憶體中的棧區,複製的時候是值傳遞給新的索引。而複雜數據類型由棧區和堆區共同儲存,棧區執行同樣的操作,只是把堆地址複製了一份,而真實數據在堆區中依然只有一份。
為了不影響原有數據,那麼我們就新建一個對象,遍歷原有對象的屬性賦值到新屬性。
let newObj = {}
for (let prop in obj) {
newObj[prop] = obj[prop]
}
上面這個迴圈也可以用Object.assign({}, obj);
來實現。
這樣做是否解決問題?未必,因為Object中可以嵌套Object,如果原有對象屬性中有複雜數據類型,那麼新的對象中也只能得到一個地址。這種情況被稱為淺複製。我們希望能將對象中的對象,無論多少層,都能複製一份,能達到這種效果的,稱為深複製。
深複製的幾種方法
首先假設有數據
let obj = {
a: 23,
b: [0, 1, [2, 3], function() {console.log('in array')}, undefined],
c: {k: 'value'},
d: function() {console.log('a')}
}
JSON.parse(JSON.stringify(obj))
let newObj = JSON.parse(JSON.stringify(obj))
newObj.newKey = 'newValue'
console.log(obj)
console.log(newObj)
如果處理對象只是簡單的鍵值對,這個方法效果不錯。
這個方法的缺點
- 無法複製函數
- 忽略
undefined
值 - 無法處理Set、Map、Symbol類型(即使用上
repalce
參數) - 原有的原型鏈會消失
迴圈引用的對象會報錯
遞歸法
因為要處理屬性的值也是Object這種情況,自然可以想到遞歸這種處理方法。
function deepCopy(oldObj, newObj) {
let obj = newObj || {}
for (let i in oldObj) {
if (oldObj[i] === obj) { // 防止迴圈引用
continue
}
if (typeof oldObj[i] === 'object') {
// obj[i] = (oldObj[i].constructor === Array) ? [] : {}
obj[i] = oldObj[i] instanceof Array ? [] : {}
deepCopy(oldObj[i], obj[i])
} else {
obj[i] = oldObj[i]
}
}
return obj;
}
這樣就能處理一個嵌套了Object和Array等複雜變數的對象。但是對象中還可能包含Date和RegExp對象、Set、Map……
Lodash庫
const lodash = require('lodash')
let newObj = lodash.cloneDeep(obj)
數組的複製
let arr = [1, 2, 3, [4, 5], {a: 1}]
let copy = arr
copy.push(6)
let copy1 = [...arr]
copy1.push(999)
let copy2 = Array.from(arr)
copy2.push(888)
let copy3 = arr.slice()
copy3.push(777)
// 以上方法都是淺拷貝
arr[4].a = 2
console.log(arr) // [ 1, 2, 3, [ 4, 5 ], { a: 2 }, 6 ]
console.log(copy1) // [ 1, 2, 3, [ 4, 5 ], { a: 2 }, 6 ]
console.log(copy2) // [ 1, 2, 3, [ 4, 5 ], { a: 2 }, 6, 888 ]
console.log(copy3) // [ 1, 2, 3, [ 4, 5 ], { a: 2 }, 6, 777 ]
參考連接
- 摸索 JS 內深拷貝的最佳實踐 - 簡單易懂的前端角 - SegmentFault 思否
- 理解JavaScript:不可變的原始值與可變的對象引用
- [ JS 進階 ] 基本類型 引用類型 簡單賦值 對象引用 - kraaas前端博客 - SegmentFault 思否
- 什麼是js深拷貝和淺拷貝及其實現方式
- js 深拷貝 vs 淺拷貝 - 掘金
- 深拷貝的終極探索(99%的人都不知道) - 顏海鏡 - SegmentFault 思否