实现深克隆有很多方式,如 JSON.parse(JSON.stringify())
、MessageChannel
,二者都有弊端:
-
JSON.parse(JSON.stringify())
:不会处理函数和一些特殊的数据结构如Map、无法处理循环引用。 -
MessageChannel
:异步、依赖浏览器环境。
详情可以看本人另一篇文章使用MessageChannel实现深度克隆。
最推荐的还是使用现有的lodash,或者自己写
初步
function deepClone(value) {
if (typeof value !== 'object' || value === null) return value
let result = Array.isArray(value) ? [] : {}
for (let key in value) {
result[key] = deepClone(value[key])
}
return result
}
但是如果是构造函数的实例,则只会克隆为一个普通对象
优化代码
function deepClone(value) {
if (typeof value !== 'object' || value === null) return value
let result = Array.isArray(value) ? [] : {}
Object.setPrototypeOf(result, Object.getPrototypeOf(value)) // 设置原型
for (let key in value) {
result[key] = deepClone(value[key])
}
return result
}
会将原型上的属性也克隆,如果只需要克隆对象的自有属性,还需要继续优化代码
优化后
function deepClone(value) {
if (typeof value !== 'object' || value === null) return value
let result = Array.isArray(value) ? [] : {}
Object.setPrototypeOf(result, Object.getPrototypeOf(value))
for (let key in value) {
if (value.hasOwnProperty(key)) {
// 只遍历自有属性
result[key] = deepClone(value[key])
}
}
return result
}
处理如下这种无限递归情况
生成一个 Map 结构的缓存,使用 WeakMap
保证对象销毁时,Map不用手动销毁,从而阻止了内存泄漏。
const cache = new WeakMap()
function deepClone(value) {
if (typeof value !== 'object' || value === null) return value
if (cache.get(value)) return cache.get(value) // 如果存在缓存,则直接返回,避免循环引用
let result = Array.isArray(value) ? [] : {}
Object.setPrototypeOf(result, Object.getPrototypeOf(value))
cache.set(value, result)
for (let key in value) {
if (value.hasOwnProperty(key)) {
// 只遍历自有属性
result[key] = deepClone(value[key])
}
}
return result
}