思路
深拷贝对象其实就是创建一个新的对象,再将旧对象上的属性或方法添加到新对象上。
当然,要考虑属性是基本类型还是引用类型。如果是基本类型,直接使用即就行;如果是引用类型,则新建一个,然后添加到新对象上;如果是方法,由于方法本质上是函数(但不完全一样, 见 Method definitions are not constructable),函数本来就是用来复用的,所以直接添加到新对象上。
代码
/**
* 对常用数据类型进行深拷贝,考虑到的情况有:
* 1. key 为`Symbol`;
* 2. 原始值为 Array/Function/Date/Set/Map/Object等其他引用类型 的情况;
* 3. 对象循环引用.
*
* @param {*} originValue
* @returns {*}
*/
function deepClone(originValue) {
// 记录在递归 originValue 的属性时, 遇到的所有引用类型属性
const refRecords = new WeakSet()
function isObject(_originValue) {
const type = typeof _originValue
return _originValue !== null && (type === 'object' || type === 'function')
}
// 给不同引用类型创建新值
function createNewValue(_originValue) {
const cName = _originValue.constructor.name
let _newValue
if (cName === 'Array') {
_newValue = []
for (const key in _originValue) {
_newValue[key] = returnNewValue(_originValue[key])
}
} else if (cName === 'Date') {
_newValue = new Date(_originValue)
} else if (cName === 'Function') {
_newValue = _originValue // 函数本来就是复用的, 因此没必要再重新拷贝一个
} else if (cName === 'RegExp') {
_newValue = new RegExp(_originValue)
} else if (cName === 'Set') {
_newValue = new Set()
// Set 本身可迭代
_originValue.forEach((value1, value2) => {
_newValue.add(returnNewValue(value1))
})
} else if (cName === 'Map') {
_newValue = new Map()
// Map 本身可迭代
_originValue.forEach((_originValue, key) => {
_newValue.set(returnNewValue(key), returnNewValue(_originValue))
})
} else {
_newValue = {}
// 遍历 _originValue 自身的属性
for (const key of Object.getOwnPropertyNames(_originValue)) {
_newValue[key] = returnNewValue(_originValue[key])
// _newValue[key] = _originValue[key] // 循环引用时直接返回原对象, 避免循环引用
}
// 遍历 _originValue 的 Symbol 属性
for (const symbolKey of Object.getOwnPropertySymbols(_originValue)) {
_newValue[symbolKey] = returnNewValue(_originValue[symbolKey])
}
}
return _newValue
}
// 实现深拷贝
function _deepClone(_originValue) {
// 非引用类型数据直接返回
if (!isObject(_originValue)) return _originValue
// 记录引用类型
refRecords.add(_originValue)
return createNewValue(_originValue)
}
// 处理循环引用问题
function returnNewValue(_ref) {
// 判断引用记录里面是否已存在 _ref 对象, 若存在,说明 _ref 对象被引用过, 直接用 _ref 赋值
return refRecords.has(_ref) ? _ref : _deepClone(_ref)
}
const clonedValue = _deepClone(originValue)
// refRecords.clear()
return clonedValue
}