2022-12-23 21:21:47
在JavaScript中实现深拷贝时,需警惕以下核心陷阱:
数据类型丢失或转换
undefined:JSON序列化会直接忽略该属性,导致拷贝后属性消失。
函数(Function):JSON不支持函数类型,拷贝后变为null或完全丢失。
Symbol:序列化时完全被忽略,属性不会出现在结果中。
BigInt:直接调用JSON.stringify()会抛出错误(如TypeError: Do not know how to serialize a BigInt)。
示例:{ a: undefined, b: () => {}, c: Symbol('test') }经JSON方法拷贝后变为空对象{}。
循环引用导致栈溢出
JSON序列化错误:当对象存在循环引用(如const obj = {}; obj.self = obj;),JSON.stringify()会抛出"Converting circular structure to JSON"错误。
递归实现风险:手动递归深拷贝若未检测循环引用,会无限递归直至栈溢出。
解决方案:使用WeakMap记录已访问对象,发现重复引用时直接返回原引用,避免重复拷贝。
特殊对象与内置类型处理不当
Date对象:JSON序列化会转为ISO字符串(如"2023-01-01T00:00:00.000Z"),反序列化后需手动转为Date实例。
RegExp对象:可能被转为空对象{}或字符串(如"/test/"),需通过new RegExp(obj.source, obj.flags)还原。
Set/Map:JSON序列化会丢失内容(如Set转为{}),需遍历元素并重新构造。
ArrayBuffer/TypedArray:二进制数据可能无法正确复制,需调用new Uint8Array(original)等方式还原。
示例:new Set([1, 2])经JSON拷贝后变为{},需手动重建Set。
原型链与属性描述符丢失
原型链断裂:深拷贝通常只复制对象自身的可枚举属性,__proto__指向的原型方法会丢失。
不可枚举属性:如通过Object.defineProperty定义的enumerable: false属性,Object.keys()无法获取,拷贝时会被忽略。
属性描述符失效:getter/setter、writable/enumerable/configurable等配置不会被保留,可能导致拷贝后属性行为异常。
示例:若原对象依赖Object.getPrototypeOf()或属性监听(如Proxy),拷贝后可能失效。
性能与边界条件
递归深度过大:复杂嵌套对象可能导致递归栈溢出,需优化为迭代实现(如使用栈或队列)。
性能开销:深拷贝需遍历所有属性并处理特殊类型,对大型对象(如包含大量DOM节点或Canvas数据)性能较差。
兼容性问题:部分环境(如旧版IE)对Symbol或Proxy支持不完善,需额外兼容处理。
推荐实践
示例代码(简化版,仅处理基本类型和循环引用)
function deepClone(obj, map = new WeakMap()) { if (obj === null || typeof obj !== 'object') return obj; if (map.has(obj)) return map.get(obj); // 处理循环引用 let clone; if (obj instanceof Date) clone = new Date(obj); else if (obj instanceof RegExp) clone = new RegExp(obj.source, obj.flags); else if (obj instanceof Set) clone = new Set([...obj]); else if (obj instanceof Map) clone = new Map([...obj]); else if (Array.isArray(obj)) clone = []; else clone = Object.create(Object.getPrototypeOf(obj)); // 保留原型链(部分实现) map.set(obj, clone); for (const key in obj) { if (obj.hasOwnProperty(key)) { // 仅拷贝自身属性(仍忽略不可枚举属性) clone[key] = deepClone(obj[key], map); } } return clone;}总结:深拷贝的复杂性远超表面,需全面考虑类型、引用、原型和描述符等问题。生产环境建议直接使用lodash.cloneDeep,避免重复造轮子。