日常使用的深复制都有同样的缺陷,就是只能实现特定的object的深度复制(比如数组和函数),不能解决循环引用
、Date
、RegExp
对象的复制。
在JS里,除Array和Object之外的数据类型的复制可以直接通过等号=来实现,但Array和Object类型的数据通过等号只是起引用作用,指向的是同一块内存地址。当源数据改变,引用的数据也同时会发生变化。
如果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只对顶层属性做了赋值,完全没有继续做递归之类的把所有下一层的属性做深拷贝。
实现了普通对象、数组和函数的深复制,但是未解决循环引用、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] }
*/
原理:可以使用一个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
}
通过对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 }
*/