加入我们要拷贝如下的对象:
const data = {
name: '巴卡巴卡',
age: 12,
ext: {
color: 'red',
cover: {
count: 3
}
}
}
首先是递归的方式实现
function deepClone1(obj) {
if(obj === null || typeof obj !== 'object') {
return obj
}
const copy = Array.isArray(obj) ? [] : {}
Object.keys(obj).forEach(key => {
copy[key] = deepClone(obj[key])
})
return copy
}
测试:
但是如果源数据递归引用,深拷贝就会报 Uncaught RangeError: Maximum call stack size exceeded 栈溢出错误
我们修改下,实现递归引用:
function deepClone(obj, hash = new WeakMap()) {
if(obj === null || typeof obj !== 'object') {
return obj
}
if (hash.has(obj)) {
return hash.get(obj)
}
const copy = Array.isArray(obj) ? [] : {}
hash.set(obj, copy)
Object.keys(obj).forEach(key => {
copy[key] = deepClone(obj[key], hash)
})
return copy
}
这里用 WeakMap 类型作为缓存,如果存在循环引用,就返回引用自身,不进行拷贝。WeakMap 的键必须是对象,且为弱引用,当没有其他引用指向缓存中的键对象时,这些对象以及它们对应的值会被垃圾回收机制自动清理,有助于避免不必要的内存占用,并简化缓存管理。
到这里深拷贝和处理循环拷贝就全部实现了。
接下来我们不用递归的方式,用循环来模拟递归实现深拷贝: 对象可以看做一棵树
a
/ \
a1 a2
| / \
1 b1 b2
| |
1 c1
|
1
用循环遍历一棵树,借助一个栈,当栈为空时就遍历完了,栈里面存储下一个需要拷贝的节点,首先我们往栈里放入种子数据,key用来存储放哪一个父元素的哪一个子元素拷贝对象,然后遍历当前节点下的子元素,如果是对象就放到栈里,否则直接拷贝。
function cloneLoop(x) {
const root = {}
const loopList = [{
parent: root,
key: undefined,
data: x
}]
while( loopList.length) {
// 深度优先遍历
const node = loopList.pop()
const parent = node.parent
const key = node.key
const data = node.data
// 初始化赋值目标,key为undefined则拷贝到父元素,否则拷贝到子元素
let res = parent
if (typeof key !== 'undefined') {
parent[key] = {}
res = {}
}
Object.keys(data).forEach(key => {
if (typeof data[key] === 'object') {
loopList.push({
parent: res,
key,
data: data[key]
})
} else {
res[key] = data[key]
}
})
}
return root
}
上面的实现不能处理循环引用,我们继续:
function cloneLoop(x) {
const root = {}
const hash = new WeakMap()
const loopList = [{
parent: root,
key: undefined,
data: x
}]
while( loopList.length) {
// 深度优先遍历
const node = loopList.pop()
const parent = node.parent
const key = node.key
const data = node.data
// 初始化赋值目标,key为undefined则拷贝到父元素,否则拷贝到子元素
let res = parent
if (typeof key !== 'undefined') {
parent[key] = {}
res = {}
}
if (hash.has(data)){
parent[key] = hash.get(data)
break;
}
hash.set(data, res)
Object.keys(data).forEach(key => {
if (typeof data[key] === 'object') {
loopList.push({
parent: res,
key,
data: data[key]
})
} else {
res[key] = data[key]
}
})
}
return root
}