【JavaScript】手写深拷贝 2.0(更新 2022-07-15)
前言
鄙人老版 js 深拷贝博客链接,当时写的存在很多不足...现在跟着方应杭老师复习了下,收获满满。
用 JSON
const b = JSON.parse(JSON.stringify(a))
缺点:
-
不支持 Date、RegExp(正则)、函数等数据;
-
不支持引用(即环状结构,类似 window.self = window)。
用递归
基础版
支持 Date、RegExp(正则)、函数等引用数据的拷贝。
const deepClone = (a) => {
let res = undefined
// 数据类型判断
if (a instanceof Object) {
// 类 判断
if (a instanceof Function) {
// 箭头函数判断
if (a.prototype) {
// 普通函数
res = function (...args) {
return a.call(this, ...args)
}
} else {
// 箭头函数
res = (...args) => {
return a.call(undefined, ...args)
}
}
} else if (a instanceof Array) {
res = []
} else if (a instanceof Date) {
res = new Date(a - 0) // 日期格式 - 0 自动转换为时间戳
} else if (a instanceof RegExp) {
res = new RegExp(a)
} else {
res = {}
}
// 递归
for (let k in a) {
if (a.hasOwnProperty(k)) {
res[k] = deepClone(a[k])
}
}
} else {
res = a
}
return res
}
测试结果:
完整版(支持引用自身的情况)
比如浏览器的 window.self = window,这个时候如果还用上面的拷贝就会导致无限递归导致栈溢出报错。
正确的做法是添加一个 map 来记录每次拷贝过的数据,如果出现重复的就不再进行拷贝和递归。(ps:为什么用 map?因为对象 key 值只能为字符串)
const deepClone = (a, cache) => {
let res = undefined
if(!cache){
cache = new Map() // 缓存不能全局,最好临时创建并递归传递
}
if (a instanceof Object) {
// 每次拷贝前判断前面是否已经拷贝过
// 如果出现 a.self = a 在这里就会返回
// 防止后续无限递归导致栈溢出
if (cache.get(a)) {
return cache.get(a)
}
if (a instanceof Function) {
if (a.prototype) {
res = function (...args) {
return a.call(this, ...args)
}
} else {
res = (...args) => {
return a.call(undefined, ...args)
}
}
} else if (a instanceof Array) {
res = []
} else if (a instanceof Date) {
res = new Date(a - 0)
} else if (a instanceof RegExp) {
res = new RegExp(a)
} else {
res = {}
}
// 每次递归前将拷贝的值就存入 map
cache.set(a, res)
for (let k in a) {
if (a.hasOwnProperty(k)) {
// 通过参数传递缓存 map
res[k] = deepClone(a[k], cache)
}
}
} else {
res = a
}
return res
}
测试结果: