日常使用的深複製都有同樣的缺陷,就是隻能實現特定的object的深度複製(比如數組和函數),不能解決循環引用
、Date
、RegExp
對象的複製。
爲什麼需要DeepCopy
在JS裏,除Array和Object之外的數據類型的複製可以直接通過等號=來實現,但Array和Object類型的數據通過等號只是起引用作用,指向的是同一塊內存地址。當源數據改變,引用的數據也同時會發生變化。
其他方案的優缺點
JSON.parse(JSON.stringify(target))
如果obj裏面有時間對象,則JSON.stringify後再JSON.parse的結果,時間將只是字符串的形式。而不是時間對象;
如果obj裏有RegExp、Error對象,則序列化的結果將只得到空對象;
如果obj裏有函數,undefined,則序列化的結果會把函數或 undefined丟失;
如果obj裏有NaN、Infinity和-Infinity,則序列化的結果會變成null
JSON.stringify()只能序列化對象的可枚舉的自有屬性,例如 如果obj中的對象是有構造函數生成的, 則使用JSON.parse(JSON.stringify(obj))深拷貝後,會丟棄對象的constructor;
如果對象中存在循環引用的情況也無法正確實現深拷貝;
Object.assign
Object.assign不是簡單的深拷貝。查閱官方文檔發現它Object.assign只對頂層屬性做了賦值,完全沒有繼續做遞歸之類的把所有下一層的屬性做深拷貝。
實現一個最基本的深複製
實現了普通對象、數組和函數的深複製,但是未解決循環引用、Date、RegExp
function deepCopy(obj) {
let _obj = Array.isArray(obj) ? [] : {}
for (let i in obj) {
_obj[i] = typeof obj[i] === 'object' ? deepCopy(obj[i]) : obj[i]
}
return _obj
}
const obj1 = {x: 2, y: {z: 3}}
obj1.fn = function add() {
return 's'
}
const obj2 = deepCopy(obj1);
console.log(obj1)
console.log(obj2)
/*
{ x: 2, y: { z: 3 }, fn: [Function: add] }
{ x: 2, y: { z: 3 }, fn: [Function: add] }
*/
Vuex源碼中的DeepCopy解決循環引用,但未解決Date和RegExp
原理:可以使用一個WeakMap結構(ES6)或者數組(ES5)存儲已經被拷貝的對象,每一次進行拷貝的時候就先向WeakMap查詢該對象是否已經被拷貝,如果已經被拷貝則取出該對象並返回。
function find(list, f) {
return list.filter(f)[0]
}
function deepCopyV(obj, cache) {
if (cache === void 0) cache = []
// just return if obj is immutable value
if (obj === null || typeof obj !== 'object') {
return obj
}
// if obj is hit, it is in circular structure
var hit = find(cache, function(c) { return c.original === obj })
if (hit) {
return hit.copy
}
var copy = Array.isArray(obj) ? [] : {}
// put the copy into cache at first
// because we want to refer it in recursive deepCopy
cache.push({
original: obj,
copy: copy
})
Object.keys(obj).forEach(function(key) {
copy[key] = deepCopy(obj[key], cache)
})
return copy
}
優化Vuex中的DeepCopy解決Date和RegExp
通過對Date和RegExp的判斷解決這部分的深複製,改造上面代碼,解決問題
// just return if obj is immutable value
const Constructor = obj.constructor
// typeof null的返回值爲object,所以可以直接省略
if (typeof obj !== 'object') {
return obj
} else if (Constructor === RegExp) {
return new Constructor(obj)
} else if (Constructor === Date) {
return new Constructor(obj.getTime())
}
//打印
const obj1 = {x: 1}
const obj2 = {x: 2}
obj1.next = obj2;
obj2.next = obj1;
obj1.fn = function add() {
return 's'
}
obj1.reg = /\s+/g
obj1.time = new Date()
const obj3 = deepCopy(obj1);
console.log(obj1)
console.log(obj2)
console.log(obj3)
輸出
{ x: 1,
next: { x: 2, next: [Circular] },
fn: [Function: add],
reg: /\s+/g,
time: 2019-11-22T06:53:35.946Z }
{ x: 2,
next:
{ x: 1,
next: [Circular],
fn: [Function: add],
reg: /\s+/g,
time: 2019-11-22T06:53:35.946Z } }
{ x: 1,
next: { x: 2, next: [Circular] },
fn: [Function: add],
reg: /\s+/g,
time: 2019-11-22T06:53:35.946Z }
*/