本文实现了对常用数据类型的深拷贝,考虑到的情况有:
- key为
Symbol; - value为Object/Array/Date/Function/RegExp/Set/Map/WeakSet/WeakMap的等情况;
- 循环引用。
基本思路
如果原始值为基本数据类型或函数等没必要深拷贝的,直接使用原始值;如果原始值是引用类型,则创建一个新的引用类型,然后往里面添加原始值的key:value,再使用这个新值,当然key和value需要看情况处理。
对于情况1,通过调用Object.getOwnPropertySymbols(value)获取所有Symbol键,然后添加到新的对象上;对于情况2,通过判断原始值的构造函数的名称,来new一个新的实例;对于情况3,声明了一个集合(refRecords)来记录已存在的引用类型原始值,在给新的实例赋值时,先判断原始值是否在refRecords中,在的话直接使用原始值,不在才调用deepClone,最后清空refRecords。
代码
/**
* @param {*} originValue
* @returns {*}
*/
function deepClone(originValue) {
// 引用记录
const refRecords = new Set()
// 判断是否为对象
function isObject(_originValue) {
const type = typeof _originValue
return _originValue !== null && (type === 'object' || type === 'function')
}
// 根据原始值的数据类型深拷贝一份新值
function createNewValue(_originValue) {
const pName = Object.getPrototypeOf(_originValue).constructor.name
let newValue
if (pName === 'Object') {
newValue = {}
// 遍历value的自身属性
for (const key of Object.getOwnPropertyNames(_originValue)) {
newValue[key] = returnNewValue(_originValue[key])
// newValue[key] = _originValue[key] // 循环引用时直接返回原对象, 避免循环引用
}
// 处理key为 Symbol
for (const symbolKey of Object.getOwnPropertySymbols(_originValue)) {
// 没必要为 Symbol 键创建新值
newValue[symbolKey] = returnNewValue(_originValue[symbolKey])
// newValue[symbolKey] = _originValue[symbolKey] // 循环引用时直接返回原对象, 避免循环引用
}
} else if (pName === 'Array') {
newValue = []
for (const key in _originValue) {
newValue[key] = returnNewValue(_originValue[key])
}
} else if (pName === 'Date') {
newValue = new Date(_originValue)
} else if (pName === 'Function') {
newValue = _originValue // 函数本来就是复用的, 因此没必要再重新拷贝一个
} else if (pName = 'RegExp') {
newValue = new RegExp(_originValue)
}
else if (pName === 'Set') {
newValue = new Set()
// Set 本身为可迭代对象
_originValue.forEach((value1, value2) => {
newValue.add(returnNewValue(value1))
})
} else if (pName === 'Map') {
newValue = new Map()
// Map 本身为可迭代对象
_originValue.forEach((_originValue, key) => {
newValue.set(returnNewValue(key), returnNewValue(_originValue))
})
} else if (pName === 'WeakSet') {
newValue = _originValue
} else if (pName === 'WeakMap') {
newValue = _originValue
}
return newValue
}
// 实现深拷贝
function _deepClone(_originValue) {
// 原始值为 Symbol时, 返回一个新 Symbol 值
if (typeof _originValue === 'symbol') {
return Symbol(_originValue.description)
}
// 不是对象类型直接返回
if (!isObject(_originValue)) return _originValue
refRecords.add(_originValue)
return createNewValue(_originValue)
}
// 处理循环引用问题
function returnNewValue(_obj) {
// 判断引用记录里面是否已存在 _obj 对象, 若存在,说明 _obj 对象被引用过, 直接用 _obj 赋值;否则递归调用
return refRecords.has(_obj) ? _obj : _deepClone(_obj)
}
const newValue = _deepClone(originValue)
// 清空引用记录
refRecords.clear()
return newValue
}
/* ----- 测试 ----- */
let obj = {
name: 'fingertips',
likes: ['apple', 'banana', 'canon', 'dog', 'emit'],
info: {
weight: '190kg',
height: '999m',
other: {
address: 'Guangdong',
contact: {
qq: '888888',
weiChat: '99999',
email: 'xxx@qq.com'
}
}
},
date: new Date(),
regEXP: /abc34/g,
sym: {
s: Symbol(909090)
},
foo() {
return (ooooo += 1)
},
[Symbol('1234567')]: 12345,
[Symbol.iterator]() {
let i = -1
return {
next: () => {
i++
return {
done: i >= this.likes.length,
value: this.likes[i]
}
}
}
},
[BigInt(9999999)]: 1234n, // 会被转换为字符串
map: new Map([
[{ map: 'map' }, 3],
[987, 896]
]),
weakMap: new WeakMap([
[{}, 8],
[new Date(), 'timer']
]),
set: new Set([1, 2, { name: 'finger' }]),
weakSet: new WeakSet([{}, { name: 'fingertips' }])
}
let o1 = deepClone(obj)
let o2 = deepClone(obj)
console.log(o1, o1 === o2)
// 测试循环引用
obj.info['obj'] = obj
obj.map.set(obj.map, obj.map)
obj.set.add(obj.set)
console.log(obj)