前端小技巧: TS实现深拷贝函数,考虑 Map, Set, 循环引用
写一个深拷贝函数
- 使用 JSON.stringify 和 parse 这类只能适合数据结构简单的,不能循环引用的,没有function的
- 普通深拷贝
- 只考虑 Object Array
- 无法转换 Map Set 和 循环引用
- 只能应对初级要求的技术一面
function cloneDeep(obj: any) {if (typeof obj !== 'object' || !obj) return objlet result:anyif (obj instanceof Array) {result = []} else {result = {}}for (let key in obj) {if (obj.hasOwnProperty(key)) {result[key] = cloneDeep(obj[key]) // 递归调用}}return result
}// 不支持的场景
const a: any = {set: new Set([10, 20, 30]),map: new Map(['x', 10], ['y', 20])
}a.self = a // 循环引用 Uncaught RangeError: Maximum call stack size exceeded
console.log(cloneDeep(a)) // 无法处理 Map Set 和 循环引用
- 上述,对set和map的数据结构不行,会把他们干掉
- 如果添加循环引用的数据,则直接报错
- 好的方案如下:
export function cloneDeep(obj: any, map = new WeakMap()): any {if (typeof obj !== 'object' || !obj) return obj// 避免循环引用const objFromMap = map.get(obj)if (objFromMap) return objFromMaplet target: any = {}map.set(obj, target)// Mapif (obj instanceof Map) {target = new Map()obj.forEach((v, k) => {const v1 = cloneDeep(v, map)const k1 = cloneDeep(k, map)target.set(k1, v1)})}// Setif (obj instanceof Set) {target = new Set()obj.forEach(v => {const v1 = cloneDeep(v, map)target.add(v1)})}// Arrayif (obj instanceof Array) {target = obj.map(item => cloneDeep(item, map))}// Objectfor (const key in obj) {const val = obj[key]target[key] = cloneDeep(val, map)}return target
}const a: any = {set: new Set([10, 20, 30]),map: new Map(['x', 10], ['y', 20])
}a.self = a // 循环引用
console.log(cloneDeep(a))
- 总结
- 功能完整性:考虑多种数据结构
- 鲁棒性,考虑循环引用
- 能否考虑到如上几点