深浅拷贝区别与实现
建议分析 lodash 的深拷贝实现
浅拷贝
只考虑对象类型
js
// 1
Object.assign({}, obj)
// 2
{...obj}
简单实现
js
function shallowCopy(obj) {
if (typeof obj !== 'object') return
let newObj = obj instanceof Array ? [] : {}
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = obj[key]
}
}
return newObj
}
深拷贝 deepclone
递归实现
深度优先
- 判断类型,正则和日期直接返回新对象
- 是函数,通过
new Function
和toString
返回 clone - 空 null或者非对象类型,直接返回原值
- 考虑循环引用,判断如果
hash
中含有直接返回hash
中的值 - 新建一个相应的
new obj.constructor
加入hash
- 遍历对象递归(普通
key
和key
是symbol
类型的情况) - key 是 symbol 类型,类似 6,也是遍历递归
深度遍历广度遍历的区别?
对于算法来说 无非就是时间换空间 空间换时间
- 深度优先不需要记住所有的节点,所以占用空间小,而广度优先需要先记录所有的节点占用空间大
- 深度优先有回溯的操作 (没有路走了需要回头) 所以相对而言时间会长一点
- 深度优先采用的是堆栈的形式,即先进后出
- 广度优先则采用的是队列的形式,即先进先出
点我查看详细
js
// 递归实现深拷贝 深度优先
function deepClone(obj, hash = new WeakMap()) {
if (obj instanceof RegExp) return new RegExp(obj)
if (obj instanceof Date) return new Date(obj)
if (typeof obj === 'function')
return new Function('return ' + obj.toString())()
// 其他基本类型,直接返回
if (obj === null || typeof obj !== 'object') return obj
// 处理循环引用的情况
if (hash.has(obj)) {
return hash.get(obj)
}
// new 一个相应的对象
// obj 为 Array,相当于 new Array()
// obj 为 Object,相当于 new Object()
// 找到的是所属类原型上的 constructor,而原型上的 constructor 指向的是当前类本身
let constr = new obj.constructor() // 为什么用这个,完成原型链的 clone,是否可以用 Object.create(obj)?TODO:
hash.set(obj, constr)
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
// 递归拷贝
constr[key] = deepClone(obj[key], hash)
}
}
// 考虑 key 是 symbol 类型的情况
let symbolObj = Object.getOwnPropertySymbols(obj)
for (let i = 0; i < symbolObj.length; i++) {
if (obj.hasOwnProperty(symbolObj[i])) {
constr[symbolObj[i]] = deepClone(obj[symbolObj[i]], hash)
}
}
return constr
}
// testing
let symbol1 = Symbol(1)
const obj1 = {
a: { b: 1 },
c: [{ d: 1 }],
e: null,
f: undefined,
[symbol1]: { a: 1 },
g: symbol1,
h: () => {
return 1
}
}
const obj2 = deepClone(obj1)
obj1.a.b = 2
obj1.c[0].d = 2
obj1[symbol1].a = 2
console.log(obj1)
console.log(obj2)
console.log(obj1.g === obj2.g)
console.log(obj1.h === obj2.h)
console.log(obj1.h(), obj2.h())
TODO fiber 实例
JSON.stringify
结合 JSON.parse
实现 clone
js
const safeJsonClone = function (obj) {
try {
return JSON.parse(JSON.stringify(obj))
} catch (err) {
return obj
}
}
了解此方案 clone
的局限性,需要了解这两个方法处理时有哪些特点,详细参见 [JSON, JSON.stringify 与 JSON.parse]
知识点
- MDN
WeakMap
- 其中的 key 必须是引用类型
- 张鑫旭 JS WeakMap 应该什么时候使用
- MDN WeakRef
- MDN FinalizationRegistry
- 你不知道的 WeakMap
instanceof
参考