深拷贝
我们知道在 JS 中,数据类型分为简单类型和引用类型,也就会出现引用的问题,那么我们想要创建一个对象的副本,不能简单地用另一个变量指向它,由于指向同一块内存地址,本质上还是浅拷贝
首先要做类型的判断
const deepClone=(x)=>{
if(x instanceof Object){
if(x instanceof Array){}
else if(x instanceof Function){}
else if(x instanceof Date){}
else if(x instanceof RegExp){}
else{}
}else{
return x
}
}
首先我们要对数据类型的判断很熟悉,不用 typeof 的原因是,typeof 判断 null 为 ‘object’,而且对于数组和普通对象无法区分,而 instanceof 利用原型链判断,比较精确
接下来就添加具体的拷贝方法
const deepClone = (x) => {
if (x instanceof Object) {
let result
if (x instanceof Array) {
result = new Array(x.length)
} else if (x instanceof Function) {
if (x.prototype) {
result = function (arg) { return x.call(this, arg) }
} else {
result = (arg) => { return x(arg) }
}
} else if (x instanceof Date) {
result = new Date(x - 0)
} else if (x instanceof RegExp) {
result = new RegExp(x.source, x.flags)
} else {
result = {}
}
for (let key in x) {
result[key] = deepClone(x[key])
}
return result
} else {
return x
}
}
这里只对普通函数和箭头函数进行处理,可以取其 prototype 来判断
对时间戳对象减去 0,可获得毫秒数,然后构造新实例
需要对对象进行递归处理,防止对象出现嵌套情况而拷贝失败
看起来没问题了,我们来试一下
const a = {
name: 'frank',
age: 18,
live: true,
color: null,
friend: undefined,
f1: function () {},
f2: () => {},
now: new Date(),
match: '/^replace',
info: {
name: 'mike',
age: 20
}
}
const b = deepClone(a)
可以看到,我们成功的拷贝了 a 的副本,即使修改 b,也不会影响 a 的值
当我们认为大功告成的时候,来观察一个现象
window 对象居然自己引用自己,我们来试下对源对象实现环引用会怎样
const a = {
name: 'frank',
age: 18,
live: true,
color: null,
friend: undefined,
f1: function () {},
f2: () => {},
now: new Date(),
match: '/^replace',
info: {
name: 'mike',
age: 20
}
}
a.self=a
const b = deepClone(a)
可以看到,由于我们使用了递归和环引用,浏览器会无限制调用函数,直到充满调用栈
所以我们应该记录下来已经拷贝过的属性,以免无尽调用,最好使用 Map 或 WeakMap 来记录,因为键值有可能是对象,普通对象无法满足需求
const cache=new Map()
const deepClone = (x) => {
if (x instanceof Object) {
if (cache.get(x)) { return cache.get(x) }
let result
if (x instanceof Array) {
result = new Array(x.length)
} else if (x instanceof Function) {
if (x.prototype) {
result = function (arg) { return x.call(this, arg) }
} else {
result = (arg) => { return x(arg) }
}
} else if (x instanceof Date) {
result = new Date(x - 0)
} else if (x instanceof RegExp) {
result = new RegExp(x.source, x.flags)
} else {
result = {}
}
cache.set(x,result)
for (let key in x) {
result[key] = deepClone(x[key])
}
return result
} else {
return x
}
}
测试结果如下
用 Map 设置了递归出口,防止环引用带来的无限递归
目前方法还有一个缺点,就是只能使用一次,因为 Map 中的值会累积进而影响下次拷贝,可以进行改造
const deepClone = (x,cache) => {
if(!cache){
cache=new Map()
}
if (x instanceof Object) {
if (cache.get(x)) { return cache.get(x) }
let result
if (x instanceof Array) {
result = new Array(x.length)
} else if (x instanceof Function) {
if (x.prototype) {
result = function (arg) {
return x.call(this, arg)
}
} else {
result = (arg) => {
return x(arg)
}
}
} else if (x instanceof Date) {
result = new Date(x - 0)
} else if (x instanceof RegExp) {
result = new RegExp(x.source, x.flags)
} else {
result = {}
}
cache.set(x, result)
for (let key in x) {
if(x.hasOwnProperty(key)){
result[key] = deepClone(x[key],cache)
}
}
return result
} else {
return x
}
}
利用函数参数来传递 Map,每一次拷贝都维护各自的参数
拷贝时对属性进行判断,不拷贝继承属性