手写 —— 深浅拷贝

lodash 插件 cloneDeep

浅拷贝

数组:slice()/concat()/Array.from()/扩展运算符
对象:Object.assign()/扩展运算符

深拷贝

  • 通过 JSON.parse(JSON.stringify(obj))

这种方法只能复制 JSON 格式支持的属性名和值,不支持的属性名和值会直接忽略:会忽略 undefined、symbol,不能序列化函数,不能解决循环引用的对象 参考MDN

JSON.parse(JSON.stringify({
[Symbol('a')]: 'abc',
b: function() {},
c: undefined,
d: Infinity,
e: NaN,
}))
// 返回 {d: null, e: null}
  • 实现简单深拷贝
function deepCopy(source) {
if (Array.isArray(source)) {
const target = []
for (const [index, value] of source.entries()) {
target[index] = deepCopy(value)
}
return target

// 简化 => return source.map(elem => deepCopy(elem))
} else if (typeof source === 'object' && source !== null) {
const target = {}
for (const [key, value] of Object.entries(source)) {
target[key] = deepCopy(value)
}
return target

// 简化 => return Object.fromEntries(Object.entries(source).map(([key, val]) => [key, deepCopy(val)]))
} else {
// 基础类型无需拷贝
return source
}
}

Object.fromEntries() 方法把键值对列表转换为一个对象,是 Object.entries 的反转

循环引用(环)

解决思路: 通过一个WeakMap来存储拷贝过的对象

let hash = new WeakMap()
if (hash.has(source)) {
return hash.get(source)
}

hash.set(source, target)

特殊对象的拷贝

// 拷贝 Function
target = new Function(`return ${source.toString()}`)()
// 拷贝 Date
target = new Date(source.getTime())

// 拷贝 RegExp
target = new RegExp(source)