尧图精选

JavaScript深拷贝与浅拷贝详解及实践指南

🕒 发布时间:2026/9/12 10:07:28 📁 来源:尧图网络
1. 深拷贝与浅拷贝的本质区别在JavaScript开发中拷贝操作是我们每天都要面对的基础问题。很多开发者虽然知道这两个概念但在实际项目中仍然会频繁踩坑。我见过太多因为拷贝不当导致的bug——从简单的状态污染到难以追踪的内存泄漏。浅拷贝Shallow Copy就像复印一张名片。你得到了一个新的副本但名片上写的电话号码其实指向的是同一个地方。修改副本的电话号码原始名片上的号码也会跟着变。这就是为什么在React中直接修改state会导致奇怪的问题const original { name: Alice, contacts: { phone: 123 } }; const shallowCopy { ...original }; shallowCopy.contacts.phone 456; console.log(original.contacts.phone); // 输出456深拷贝Deep Copy则是完全重建整个数据结构。就像不仅复印了名片还新建了一个完全独立的电话系统。修改副本不会影响原始数据const deepCopy JSON.parse(JSON.stringify(original)); deepCopy.contacts.phone 789; console.log(original.contacts.phone); // 仍为456警告JSON方法处理不了函数、undefined等特殊类型后面我们会介绍更可靠的深拷贝方案2. 为什么需要区分深浅拷贝2.1 内存管理的底层逻辑JavaScript中的变量分为基本类型string、number等和引用类型object、array等。基本类型直接存储在栈内存中而引用类型在栈中存储的是堆内存地址。当执行浅拷贝时基本类型创建真正的独立副本引用类型只复制内存地址指针// 基本类型的拷贝是安全的 let a 1; let b a; b 2; console.log(a); // 仍然是1 // 引用类型的危险操作 const objA { val: 1 }; const objB objA; objB.val 2; console.log(objA.val); // 变成了22.2 实际开发中的典型场景状态管理在Redux中reducer必须返回全新的state对象配置对象避免修改传入的配置参数函数参数纯函数不应该修改输入参数缓存数据需要隔离原始数据和缓存副本我曾在一个电商项目中遇到惨痛教训商品列表的筛选功能污染了原始数据导致用户看到的商品价格随机变化。最后发现是某个筛选函数直接修改了传入的数组。3. 实现深拷贝的几种方式与陷阱3.1 JSON方法的致命缺陷虽然JSON.parse(JSON.stringify())是最常用的深拷贝方式但它存在严重局限const problemObj { date: new Date(), // 会被转成字符串 fn: () {}, // 函数会消失 undef: undefined, // 会丢失 infinity: Infinity, // 变成null regex: /pattern/i // 变成空对象 }; console.log(JSON.parse(JSON.stringify(problemObj))); /* 输出 { date: 2023-07-20T08:00:00.000Z, infinity: null, regex: {} } */3.2 手写递归深拷贝函数这是最可靠的解决方案下面是我在项目中使用的增强版function deepClone(source, hash new WeakMap()) { if (source null || typeof source ! object) { return source; } // 处理循环引用 if (hash.has(source)) return hash.get(source); const target Array.isArray(source) ? [] : {}; hash.set(source, target); // 处理Symbol属性 const symKeys Object.getOwnPropertySymbols(source); if (symKeys.length) { symKeys.forEach(symKey { target[symKey] deepClone(source[symKey], hash); }); } // 处理普通属性 for (let key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] deepClone(source[key], hash); } } // 保持原型链 const proto Object.getPrototypeOf(source); Object.setPrototypeOf(target, proto); return target; }这个方案解决了循环引用通过WeakMapSymbol属性原型链继承各种边界情况3.3 第三方库方案对比库名称优点缺点适用场景lodash.cloneDeep功能完善、性能好增加包体积大型项目rfdc专门优化、极快功能较基础性能敏感场景structuredClone浏览器原生API兼容性问题现代浏览器环境实测建议中小项目用structuredClone需polyfill大型项目用lodash4. 性能优化与特殊场景处理4.1 避免不必要的深拷贝不是所有情况都需要深拷贝。在某些场景下我们可以用更聪明的方式不可变数据使用Immutable.js部分更新配合展开运算符const newState { ...oldState, user: { ...oldState.user, name: New Name } };冻结对象Object.freeze()防止意外修改4.2 处理特殊对象类型不同类型的对象需要特殊处理function cloneSpecial(obj) { if (obj instanceof Date) return new Date(obj); if (obj instanceof RegExp) return new RegExp(obj); if (obj instanceof Map) return new Map(cloneSpecial([...obj])); if (obj instanceof Set) return new Set(cloneSpecial([...obj])); // 其他特殊类型... }4.3 性能实测数据对1000层嵌套对象进行拷贝测试方法耗时(ms)内存占用(MB)JSON方法128.2递归深拷贝2810.5lodash.cloneDeep159.1structuredClone97.85. 与防抖/节流的联动应用在网络请求场景中我们经常需要处理深拷贝与函数优化的组合// 带深拷贝的防抖函数 function debounceWithClone(fn, delay) { let timer; return function(...args) { clearTimeout(timer); timer setTimeout(() { const clonedArgs deepClone(args); fn.apply(this, clonedArgs); }, delay); }; } // 使用示例 const processData debounceWithClone(data { // 安全地操作数据副本 console.log(Processing:, deepClone(data)); }, 300);这种模式特别适合处理表单提交防抖实时搜索建议大体积数据批处理在Promise链中处理深拷贝的推荐方式fetchData() .then(response deepClone(response.json())) .then(data { // 安全操作 });
上一篇/下一篇内容由系统自动关联 返回资讯列表 →