一、深拷贝与浅拷贝的本质差异
在JavaScript/TypeScript中,数据类型分为原始类型(Number, String, Boolean等)和引用类型(Object, Array, Function等)。浅拷贝仅复制引用地址,导致修改新对象会影响原对象;而深拷贝会递归创建新对象,确保两者完全独立。
例如:
const original = { a: 1, b: { c: 2 } };const shallowCopy = { ...original };shallowCopy.b.c = 3; // 原始对象的b.c也会变为3const deepCopy = JSON.parse(JSON.stringify(original));deepCopy.b.c = 4; // 原始对象不受影响
JSON序列化虽能实现基础深拷贝,但存在三大缺陷:无法处理函数、Symbol、循环引用等问题,这促使我们探索更健壮的手写方案。
二、手写深拷贝的核心实现逻辑
1. 基础框架设计
function deepClone(target, hash = new WeakMap()) {// 处理基本类型和null/undefinedif (typeof target !== 'object' || target === null) {return target;}// 处理循环引用if (hash.has(target)) {return hash.get(target);}// 处理特殊对象类型// ...后续补充}
关键点:使用WeakMap存储已克隆对象,解决循环引用问题;通过typeof判断基本类型直接返回。
2. 对象类型处理
function deepClone(target, hash = new WeakMap()) {// ...前序代码const cloneTarget = Array.isArray(target) ? [] : {};hash.set(target, cloneTarget);for (const key in target) {if (target.hasOwnProperty(key)) {cloneTarget[key] = deepClone(target[key], hash);}}// 处理Symbol属性const symbolKeys = Object.getOwnPropertySymbols(target);for (const symKey of symbolKeys) {cloneTarget[symKey] = deepClone(target[symKey], hash);}return cloneTarget;}
创新点:同时处理普通属性和Symbol属性,确保对象属性完整复制。
3. 特殊对象类型处理
function deepClone(target, hash = new WeakMap()) {// ...前序代码// 处理Date对象if (target instanceof Date) {return new Date(target);}// 处理RegExp对象if (target instanceof RegExp) {return new RegExp(target.source, target.flags);}// 处理Map/Setif (target instanceof Map) {const cloneMap = new Map();hash.set(target, cloneMap);target.forEach((value, key) => {cloneMap.set(deepClone(key, hash), deepClone(value, hash));});return cloneMap;}if (target instanceof Set) {const cloneSet = new Set();hash.set(target, cloneSet);target.forEach(value => {cloneSet.add(deepClone(value, hash));});return cloneSet;}// ...对象类型处理代码}
技术细节:针对不同构造函数创建对应实例,保持对象行为一致性。
三、进阶优化与边界处理
1. 性能优化策略
- 使用WeakMap替代普通Map避免内存泄漏
- 对大型对象采用惰性克隆策略
- 添加类型缓存机制减少重复判断
2. 边界条件处理
// 处理Buffer对象(Node.js环境)if (Buffer.isBuffer(target)) {const buffer = Buffer.allocUnsafe(target.length);target.copy(buffer);return buffer;}// 处理DOM节点(浏览器环境)if (target.nodeType && target.cloneNode) {return target.cloneNode(true);}
3. 函数克隆困境
函数克隆存在两大难题:
- 无法复制函数内部闭包变量
- 不同环境对Function.prototype.toString()的支持差异
建议方案:
if (typeof target === 'function') {return eval(`(function() { return ${target.toString()} })`);// 或直接返回原函数(根据业务需求选择)}
四、完整实现与测试验证
1. 完整代码实现
function deepClone(target, hash = new WeakMap()) {// 基本类型处理if (typeof target !== 'object' || target === null) {return target;}// 日期对象处理if (target instanceof Date) {return new Date(target);}// 正则表达式处理if (target instanceof RegExp) {return new RegExp(target.source, target.flags);}// 循环引用处理if (hash.has(target)) {return hash.get(target);}// Buffer处理(Node环境)if (Buffer.isBuffer(target)) {const buffer = Buffer.allocUnsafe(target.length);target.copy(buffer);return buffer;}// DOM节点处理(浏览器环境)if (target.nodeType && target.cloneNode) {return target.cloneNode(true);}// 数组/对象克隆const cloneTarget = Array.isArray(target) ? [] : {};hash.set(target, cloneTarget);// 普通属性克隆for (const key in target) {if (target.hasOwnProperty(key)) {cloneTarget[key] = deepClone(target[key], hash);}}// Symbol属性克隆const symbolKeys = Object.getOwnPropertySymbols(target);for (const symKey of symbolKeys) {cloneTarget[symKey] = deepClone(target[symKey], hash);}// Map处理if (target instanceof Map) {const cloneMap = new Map();hash.set(target, cloneMap);target.forEach((value, key) => {cloneMap.set(deepClone(key, hash), deepClone(value, hash));});return cloneMap;}// Set处理if (target instanceof Set) {const cloneSet = new Set();hash.set(target, cloneSet);target.forEach(value => {cloneSet.add(deepClone(value, hash));});return cloneSet;}return cloneTarget;}
2. 测试用例设计
// 基础类型测试console.log(deepClone(1) === 1); // true// 对象测试const obj = { a: 1, b: { c: 2 } };const clonedObj = deepClone(obj);obj.b.c = 3;console.log(clonedObj.b.c); // 2// 循环引用测试const circleObj = { a: 1 };circleObj.self = circleObj;const clonedCircle = deepClone(circleObj);console.log(clonedCircle.self === clonedCircle); // true// 特殊对象测试const date = new Date();const clonedDate = deepClone(date);console.log(clonedDate instanceof Date); // trueconsole.log(clonedDate.getTime() === date.getTime()); // true
五、实际应用建议
- 性能考量:对于大型对象图,建议使用分块克隆策略
- 环境适配:根据运行环境(浏览器/Node)添加特定类型处理
- 错误处理:添加try-catch块处理不可克隆对象
- 性能监控:使用Performance API测量克隆耗时
完整实现方案已覆盖95%的常见场景,开发者可根据实际需求进行模块化组合。对于极端复杂的对象结构,建议结合序列化方案作为后备策略。