一、深浅拷贝的核心概念
1.1 什么是深/浅拷贝?
浅拷贝:当我们将对象A赋值给对象B时,如果修改B的属性会影响A。它只复制对象的第一层属性,深层属性仍然共享内存地址。
const objA = { a: 1, b: { c: 2 } };
const objB = Object.assign({}, objA);
objB.b.c = 3;
console.log(objA.b.c); // 输出3
深拷贝:会递归复制对象的所有层级属性,新旧对象完全独立,互不影响。这是处理复杂数据结构的必备技能。
1.2 浅拷贝 vs 深拷贝内存模型

1.3 循环引用处理原理

二、JSON方法的致命缺陷
JSON.parse(JSON.stringify()) 虽能解决部分场景,但存在三大硬伤:
-
循环引用报错:当对象内部属性循环引用时会抛出异常
-
特殊对象丢失:无法处理RegExp、Date、Set、Map等类型
-
函数属性丢失:函数类型属性会被直接忽略
三、手写深拷贝进阶代码详解
3.1 基础版实现
/**
* 基础版深拷贝
* @param {any} target 需要拷贝的对象
* @returns {any} 拷贝后的新对象
*/
function baseDeepClone(target) {
// 基本类型直接返回
if (typeof target !== 'object' || target === null) {
return target;
}
// 初始化克隆容器
const cloneTarget = Array.isArray(target) ? [] : {};
// 递归拷贝属性
for (let key in target) {
if (Object.prototype.hasOwnProperty.call(target, key)) {
cloneTarget[key] = baseDeepClone(target[key]);
}
}
return cloneTarget;
}
3.2 处理循环引用
使用WeakMap存储已拷贝对象,检测到循环引用时直接返回存储值:
/**
* 支持循环引用的深拷贝
* @param {any} target 需要拷贝的对象
* @param {WeakMap} map 循环引用缓存Map
* @returns {any} 拷贝后的新对象
*/
function advancedDeepClone(target, map = new WeakMap()) {
// 基本类型处理
if (typeof target !== 'object' || target === null) {
return target;
}
// 已拷贝对象直接返回
if (map.has(target)) {
return map.get(target);
}
// 根据构造函数初始化
const constructor = target.constructor;
const cloneTarget = new constructor();
// 注册已拷贝对象
map.set(target, cloneTarget);
// 特殊对象处理分支
if (constructor === Date) return new Date(target);
if (constructor === RegExp) return new RegExp(target);
// 递归处理属性
if (constructor === Map) {
target.forEach((value, key) => {
cloneTarget.set(
advancedDeepClone(key, map),
advancedDeepClone(value, map)
);
});
return cloneTarget;
}
if (constructor === Set) {
target.forEach(value => {
cloneTarget.add(advancedDeepClone(value, map));
});
return cloneTarget;
}
// 处理普通对象/数组
Reflect.ownKeys(target).forEach(key => {
cloneTarget[key] = advancedDeepClone(target[key], map);
});
return cloneTarget;
}
3.3 处理特殊对象类型
通过Object.prototype.toString.call()精准识别对象类型:
const handleSpecialObjects = (target, type) => {
const Ctor = target.constructor;
switch(type) {
case '[object Date]':
return new Ctor(target);
case '[object RegExp]':
return new Ctor(target.source, target.flags);
case '[object Function]':
return cloneFunction(target);
// 处理其他类型...
}
}
3.4 函数拷贝的黑科技
function cloneFunction(func) {
if (!func.prototype) return func; // 箭头函数直接返回
const bodyReg = /(?<={)(.|\n)+(?=})/m;
const paramReg = /(?<=\().+(?=\)\s*{)/;
const funcString = func.toString();
const params = paramReg.exec(funcString);
const body = bodyReg.exec(funcString);
return new Function(...(params?.[0].split(',') || []), body?.[0] || '');
}
四、完整深拷贝实现方案
const completeDeepClone = (target, map = new WeakMap()) => {
// 类型检测与特殊处理
if (target === null) return null;
if (typeof target !== 'object') return target;
const type = Object.prototype.toString.call(target);
if (map.get(target)) return map.get(target);
let cloneTarget;
switch(type) {
case '[object Array]':
case '[object Object]':
cloneTarget = new target.constructor();
map.set(target, cloneTarget);
for (const key in target) {
if (target.hasOwnProperty(key)) {
cloneTarget[key] = completeDeepClone(target[key], map);
}
}
break;
case '[object Date]':
cloneTarget = new Date(target);
break;
case '[object RegExp]':
// 其他类型处理...
}
return cloneTarget;
}
五、深拷贝的四大应用场景
5.1 Vue/React状态管理
// Vue3组合式API示例
import { reactive } from 'vue';
const state = reactive({
user: {
name: 'John',
permissions: ['read', 'write']
}
});
// 修改前创建副本
const tempState = advancedDeepClone(state);
tempState.user.permissions.push('delete');
// 提交修改
const commitChanges = () => {
Object.assign(state, tempState);
};
5.2 数据版本快照
class DataHistory {
constructor(initialState) {
this.history = [advancedDeepClone(initialState)];
this.currentIndex = 0;
}
commit(newState) {
this.history = this.history.slice(0, this.currentIndex + 1);
this.history.push(advancedDeepClone(newState));
this.currentIndex++;
}
undo() {
this.currentIndex = Math.max(0, this.currentIndex - 1);
return advancedDeepClone(this.history[this.currentIndex]);
}
}
5.3 跨线程数据传递
// Web Worker通信示例
const worker = new Worker('data-processor.js');
// 准备大数据集
const largeDataset = {
/* 10万条数据 */
};
// 安全传递数据
worker.postMessage({
type: 'process',
payload: advancedDeepClone(largeDataset)
});
// 接收处理结果
worker.onmessage = ({ data }) => {
const safeData = advancedDeepClone(data);
// 使用安全数据
};
5.4 配置对象保护
const baseConfig = {
apiUrl: 'https://api.example.com',
timeout: 5000,
headers: {
'Content-Type': 'application/json'
}
};
function createService(config) {
// 创建不可变配置
const safeConfig = advancedDeepClone(config);
Object.freeze(safeConfig);
return {
get: (url) => {
return fetch(`${safeConfig.apiUrl}${url}`, {
timeout: safeConfig.timeout,
headers: safeConfig.headers
});
}
};
}
六、高频面试题解析
Q1:WeakMap在深拷贝中的作用?
-
弱引用特性:不阻止垃圾回收机制,当键对象没有其他引用时可以被自动回收,防止内存泄漏
-
循环引用检测:存储已拷贝对象引用,快速识别循环引用
-
性能优化:相比Map使用弱引用,更适合临时缓存场景
-
示例对比:
// 使用Map可能导致内存泄漏 const map = new Map(); let obj = {}; map.set(obj, true); obj = null; // Map仍保持对对象的引用 // 使用WeakMap自动释放 const weakMap = new WeakMap(); let obj2 = {}; weakMap.set(obj2, true); obj2 = null; // 自动从WeakMap中移除
Q2:如何保持拷贝对象的原型链?
-
使用
target.constructor获取正确的构造函数 -
通过
new constructor()创建实例,继承原型方法 -
特殊处理原型方法的情况
function clonePrototype(target) { // 错误方式:直接创建空对象 // const clone = {}; // 正确方式:保持原型链 const clone = Object.create( Object.getPrototypeOf(target) ); // 复制自有属性 Reflect.ownKeys(target).forEach(key => { clone[key] = advancedDeepClone(target[key]); }); return clone; }
Q3:为什么不用Map而用WeakMap?
-
函数类型判断
function isArrowFunction(fn) {
return typeof fn === 'function' && !fn.prototype;
}
-
函数体解析实现
function cloneFunction(fn) {
// 箭头函数直接返回引用
if (isArrowFunction(fn)) return fn;
// 解析函数参数和内容
const functionString = fn.toString();
const bodyMatch = functionString.match(/{([\s\S]*)}/);
const paramMatch = functionString.match(/\((.*?)\)/);
// 构建新函数
try {
return new Function(
paramMatch ? paramMatch[1].split(',') : [],
bodyMatch ? bodyMatch[1] : ''
);
} catch (e) {
// 失败时返回原函数
return fn;
}
}
七、性能优化建议
-
对象类型预判:在递归前先进行类型检测
-
循环检测优化:使用WeakMap替代普通对象存储
-
并行处理:对大型数组使用Web Worker分片处理
-
缓存策略:对常用类型(Date等)创建缓存池

通过本文的学习,读者应该能够:
理解深拷贝的核心原理
手动实现生产级深拷贝方法
在项目中合理应用深拷贝
从容应对相关技术面试

1661

被折叠的 条评论
为什么被折叠?



