Open liangbus opened 4 years ago
翻车了 对于数组数据的拷贝,上面的代码是有问题的, 因为 [].concat 只做了一层浅拷贝 测试:
var arr1 = [{a: 1, b: 2, c: 3}, {d: 4, e: 'hey hey'}, {f: false, g: []}]
var copy1 = cloneDeep(arr1)
copy1[0] === arr1[0] // true
var t = arr1[1]
t.e = 'string changed'
copy1[1] // {d: 4, e: "string changed"}
可见,这里仅仅是拷贝了其内存地址,并不是真正的拷贝其内容
经修改验证后的代码
/**
* 深拷贝
* @param target 拷贝目标
* @param hash 存放对象的 map,防止循环引用
*/
function cloneDeep(target, hash = new WeakMap()) {
// 基本类型或者 function 类型的直接返回值
if (typeof target !== 'object' || hash.has(target)) return target
// 数组类型
if(Array.isArray(target)) {
const res = []
for(let item of target){
if(typeof item === 'object') {
res.push(cloneDeep(item))
} else {
res.push(item)
}
}
// hash.set(target, target)
// 此处不能直接用 concat,concat 是浅拷贝,如果元素是对象类型,只会拷贝其地址
// return [].concat(target)
return res
} else if (typeof target === 'object') { // 对象类型
// 遍历对象中包含 Symbol 类型的所有 key 值,存到一个累加器当中
return [...Object.keys(target), ...Object.getOwnPropertySymbols(target)].reduce((res, curKey) => {
if (typeof target[curKey] === 'object' && !Array.isArray(target[curKey])) {
// 这里只有 object 对象才会有循环引用的可能,需排除数组类型,否则对象下的数组仍然是浅拷贝
hash.set(target[curKey], target[curKey])
}
res[curKey] = cloneDeep(target[curKey], hash)
return res
// 初始化参数,假如迭代的对象非最外层级,则以当前对象的原型创建一个新的对象作为下一层递归的初始值
}, target.constructor === Object ? {} : Object.create(target.constructor.prototype))
}
}
对数组做一层遍历然后再次递归 验证:
var arr2 = [{a: 1, b: 2, c: 3}, {d: 4, e: 'hey hey'}, {f: false, g: []}]
var copy2 = cloneDeep(arr2)
copy2[0] === arr2[0] // false
var o = arr2[2]
o.f = true
o.g.push('Wow~')
arr2[2] // {f: true, g: Array(1)}
copy2[2] // {f: false, g: Array(0)}
前几天看到掘金一个人发帖说一行代码实现 deepClone(记得好像还是个企业号), 点进去果然一片骂声,没错,就是通过 stringify 实现的。
JSON.stringify
骂他其实是因为他的文章太水,并非用 stringify 百分百是错的,假如不考虑安全性,不考虑 function,undefined,Symbol 这些值
MDN 里面说明了
JSON.stringify 还是能满足简单的要求的
递归
上面的是简单版本,我们再来完善一下,要能够兼容上面所不支持的一些类型 通过递归,很简单就能实现,直接看代码
普通的 Object 类型数据,用上面的是基本够用了
那什么时候会不够用??? 对象里面出现循环引用的时候,递归会找不到边界而陷入无穷递归,出现爆栈
加入 WeakMap 缓存对象
针对上述问题,加一个对象存储起来就好了,前阵子刚学的 WeakMap, WeakSet 正好排上了用场
到这里,一个深拷贝函数已经基本实现了,基本上能适用于平常的开发场景,但是看了一下 lodash 里面的 cloneDeep 的源码之后,发现事情并没有那么简单,应该还是有相当一部分边界没有覆盖到吧,如果为保险起见,用 lodash 里面的吧,省事~
参考: JSON.stringify() - MDN