新闻详情

新闻详情

首页 / 资讯中心 / 详情

彻底理解 JavaScript 浅拷贝与深拷贝:原理、实现与应用

发布时间:2026/9/26 3:16:13来源:尧图网络
彻底理解 JavaScript 浅拷贝与深拷贝:原理、实现与应用
1. 从一次线上脏数据说起为什么浅拷贝会“串味”前端项目里最让人后背发凉的 bug往往不是报错而是数据悄悄被改了。我遇到过一个典型场景列表页把接口返回的userList存进状态详情弹窗打开时想“复制一份”做临时编辑于是写了const draft { ...currentUser }。用户改完取消列表里的头像和昵称却跟着变了。排查半天才发现currentUser里嵌套了profile和tags展开运算符只复制了第一层嵌套对象仍然是同一个内存地址。这就是浅拷贝与深拷贝要解决的核心问题。JavaScript 里值类型string、number、boolean、null、undefined、symbol、bigint赋值时复制的是值本身而引用类型Object、Array、Date、RegExp、Function等赋值时复制的是内存地址。浅拷贝只处理第一层嵌套引用依然共享深拷贝递归复制每一层最终两份数据完全独立。这篇文章面向正在做状态管理、配置合并、缓存复用的前端开发者。我会把可复制的拷贝工具函数、structuredClone与 JSON 方案的对比配置、以及引用嵌套、循环引用、特殊对象三类验证用例和断言动作都交付出来。你可以直接拿去改项目里的工具目录也可以当成面试前的实操复习。理解拷贝的本质本质上是在理解“引用共享”这件事会在哪些地方咬你一口。2. 前置准备用 TaoToken 快速搭一个可验证的 JS 实验环境要验证拷贝行为最省事的方式是有一个能随时跑 Node 或浏览器控制台的环境。如果你本地已经有 Node直接node进 REPL 就行。但如果你想让 AI 帮你解释某段拷贝代码为什么输出不符合预期或者让它生成针对循环引用的测试用例可以借助 TaoToken 的模型对话能力来加速排查。TaoToken 是一个聚合多家大模型能力的 API 平台对前端开发者来说它的价值在于你不需要在多个模型控制台之间来回切换用一个 API Key 就能在脚本里调用不同模型做代码解释、用例生成和报错分析。官网地址是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 入口是 https://taotoken.net/api 这个不加 UTM。具体操作上你可以先到控制台创建 Keyhttps://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 然后在 API Keys 页面拿到密钥https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 。拿到之后就可以在 Node 脚本里发请求让模型帮你判断一段深拷贝实现是否漏掉了Symbol键或循环引用。如果你更习惯在对话界面里贴代码问问题直接用模型对话入口即可https://taotoken.net/model-chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel-chatutm_campaignrewrite 。接入细节和参数说明在文档里https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。长期做编码和 Agent 场景的话可以了解 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 。注意TaoToken 在这里的角色是帮你解释代码、生成测试用例和排查报错的辅助工具不是替代你的编辑器或运行时。拷贝逻辑最终还是要靠本地断言来验证。3. 可复制配置浅拷贝、深拷贝与 structuredClone 的完整实现3.1 浅拷贝的三种写法与它们的边界对象浅拷贝最常用的是展开运算符和Object.assignconst source { name: Alice, profile: { city: Hangzhou }, tags: [a, b] }; const copy1 { ...source }; const copy2 Object.assign({}, source); copy1.profile.city Shanghai; console.log(source.profile.city); // Shanghai嵌套对象共享引用数组浅拷贝可以用展开、slice()、concat()const arr [1, 2, { a: 3 }]; const arrCopy [...arr]; arrCopy[2].a 99; console.log(arr[2].a); // 99嵌套元素共享引用这三种写法都只复制第一层。判断标准很简单只要你的数据结构里存在嵌套的引用类型浅拷贝就会留下共享隐患。3.2 手写深拷贝处理循环引用与特殊对象下面这个deepClone是我在项目里实际用过的版本核心是WeakMap记录已复制对象避免循环引用导致栈溢出同时单独处理Date、RegExp、Map、Setfunction deepClone(target, visited new WeakMap()) { // 值类型与 null 直接返回 if (target null || typeof target ! object) { return target; } // 特殊内置对象 if (target instanceof Date) return new Date(target.getTime()); if (target instanceof RegExp) return new RegExp(target.source, target.flags); if (target instanceof Map) { const mapCopy new Map(); visited.set(target, mapCopy); target.forEach((value, key) { mapCopy.set(deepClone(key, visited), deepClone(value, visited)); }); return mapCopy; } if (target instanceof Set) { const setCopy new Set(); visited.set(target, setCopy); target.forEach((value) setCopy.add(deepClone(value, visited))); return setCopy; } // 循环引用已复制过就直接返回缓存 if (visited.has(target)) { return visited.get(target); } const clone Array.isArray(target) ? [] : {}; visited.set(target, clone); // 复制自身可枚举属性包含 Symbol 键 Reflect.ownKeys(target).forEach((key) { clone[key] deepClone(target[key], visited); }); return clone; }这里用Reflect.ownKeys而不是for...in是为了把Symbol键也复制进去同时避免遍历到原型链上的属性。WeakMap的键是对象不会阻止垃圾回收比Map更适合做这种临时缓存。3.3 structuredClone 与 JSON 方案对比现代浏览器和 Node 17 提供了原生structuredClone它基于结构化克隆算法能处理循环引用、Date、RegExp、Map、Set、ArrayBuffer等但不能克隆函数、Symbol键和 DOM 节点。const obj { a: 1, b: undefined, c: Symbol(key), d: new Date(2024-01-01), e: /abc/g, f: function () { return 1; }, }; obj.self obj; // 循环引用 const clone structuredClone(obj); console.log(clone.d instanceof Date); // true console.log(clone.self clone); // true console.log(clone.f); // undefined函数无法克隆 console.log(clone.c); // undefinedSymbol 键丢失JSON 方案JSON.parse(JSON.stringify(obj))的局限更明显undefined、Symbol、函数会被直接丢弃Date会变成字符串循环引用会抛TypeError。下面这张表可以帮你快速选型方案循环引用Date/RegExp函数/SymbolMap/Set性能展开运算符不处理共享引用共享引用共享引用高JSON 方案报错Date 变字符串丢失丢失中手写 deepClone支持支持Symbol 键支持函数共享支持中低structuredClone支持支持不支持支持较高提示如果你的数据只包含 JSON 安全类型且没有循环引用JSON 方案够用一旦涉及Date、Map、循环引用优先structuredClone需要保留Symbol键或做精细控制用手写deepClone。4. 验证请求与成功结果三类用例的断言动作4.1 引用嵌套用例const nested { level1: { level2: { value: 1 } } }; const shallow { ...nested }; const deep deepClone(nested); shallow.level1.level2.value 100; console.assert(nested.level1.level2.value 100, 浅拷贝应共享嵌套引用); deep.level1.level2.value 200; console.assert(nested.level1.level2.value 100, 深拷贝不应影响原对象);运行后第一个断言通过说明浅拷贝确实共享了level2第二个断言通过说明深拷贝切断了引用链。4.2 循环引用用例const circular { name: root }; circular.self circular; const cloned deepClone(circular); console.assert(cloned.self cloned, 循环引用应指向克隆后的自身); console.assert(cloned ! circular, 克隆对象与原对象不是同一个引用);如果不用WeakMap这段代码会直接RangeError: Maximum call stack size exceeded。加上缓存后cloned.self正确指向cloned本身。4.3 特殊对象用例const special { date: new Date(2024-06-01), regex: /hello/gi, map: new Map([[k, { v: 1 }]]), set: new Set([1, 2, 3]), sym: Symbol(s), }; special[special.sym] symbol-value; const specialClone deepClone(special); console.assert(specialClone.date instanceof Date, Date 类型应保留); console.assert(specialClone.regex instanceof RegExp, RegExp 类型应保留); console.assert(specialClone.map instanceof Map, Map 类型应保留); console.assert(specialClone[special.sym] symbol-value, Symbol 键应被复制);四个断言全部通过说明手写deepClone在特殊对象上没有丢类型。如果你换成 JSON 方案date会变成字符串map和set会变成空对象Symbol键直接消失。5. 本篇常见错排查深拷贝踩过的坑5.1 展开运算符被当成深拷贝最常见的误区就是const copy { ...obj }之后直接改嵌套属性。判断方法改完嵌套属性后看原对象有没有变。如果变了说明你用的是浅拷贝。解决方案是对嵌套层级明确使用deepClone或者只对需要隔离的那一层做深拷贝。5.2 JSON 方案丢字段却不报错JSON.parse(JSON.stringify(obj))最危险的地方在于它不报错只是静默丢数据。undefined、函数、Symbol键都会消失Date变成 ISO 字符串。如果你在配置合并场景里用了这个方案合并后的配置可能缺少关键字段而且很难从日志里看出来。排查时可以用Object.keys对比原对象和克隆对象的键数量。5.3 循环引用导致栈溢出当对象存在obj.self obj这类结构时递归深拷贝如果没有缓存机制会无限递归直到栈溢出。报错信息通常是RangeError: Maximum call stack size exceeded。解决方案就是前面deepClone里的WeakMap每次进入递归前先检查是否已经复制过。5.4 structuredClone 克隆函数失败structuredClone遇到函数会抛DataCloneError遇到Symbol键会直接忽略。如果你从接口拿到的数据里混入了回调函数直接structuredClone会中断整个流程。排查时可以先JSON.stringify看看有没有函数字段或者用typeof逐层检查。5.5 深拷贝性能拖垮高频操作在requestAnimationFrame或高频事件回调里做深拷贝很容易造成卡顿。深拷贝的时间复杂度随嵌套层级增长数据量大时开销明显。优化思路是只对真正需要隔离的字段做深拷贝其余保持浅拷贝或者改用不可变数据结构从源头避免拷贝。6. 选型建议与后续接入回到实际工程我的经验是状态管理里如果用了 Redux 或 Pinia优先遵循不可变更新原则用展开运算符做浅层更新只在需要完整快照时用深拷贝。配置合并场景里如果配置对象包含Date或嵌套数组直接上deepClone或structuredClone别用 JSON 方案。缓存复用场景要特别注意缓存对象被外部修改后下次读出来的可能已经是脏数据。如果你想让 AI 帮你审查一段拷贝代码是否覆盖了循环引用和特殊对象可以把代码贴到模型对话里https://taotoken.net/model-chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel-chatutm_campaignrewrite 。需要在自己的 Node 脚本里批量生成测试用例就去 API Keys 页面拿 Keyhttps://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 接入方式参考文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。长期做编码 Agent 的话Coding Plan 入口在这里https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 。最后留一个实用技巧在项目里建一个clone.js把deepClone和几个断言用例放进去每次改动拷贝逻辑就跑一遍node clone.js。断言不通过就说明有引用没切断比等到线上出脏数据再排查省事得多。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

Java开发用Cursor AI编程全流程:接入MCP服务与rules配置,让AI释放你的双手! 2026/9/26 3:53:34

Java开发用Cursor AI编程全流程:接入MCP服务与rules配置,让AI释放你的双手!

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
怎么降AIGC最有效?推荐8款降AI率网站 2026/9/26 3:53:34

怎么降AIGC最有效?推荐8款降AI率网站

高校对论文AIGC检测的收紧已经是公开事实,知网、维普陆续上线AI检测功能后,降AI率成了毕业季的硬需求。市面上号称能降AI率的工具数量激增,但真正有效的并不多,多数只是把同义词替换包装成"智能降重",处理完…

阅读更多 →
2026婚礼邀请函设计全流程复盘:从关键词搜索到版式印刷 2026/9/26 3:53:28

2026婚礼邀请函设计全流程复盘:从关键词搜索到版式印刷

做了这么多次婚礼邀请函,我越来越觉得,一张请柬其实是整场婚礼的“预告片”。宾客拿到手里那几秒钟,基本就能脑补出这场婚礼是户外轻松派对,还是酒店里的大气晚宴,也大概能猜到新人花了多少心思在这件事上。这次做2026…

阅读更多 →
Codex 结合 better-icons 拿图标:先固定图标库再动手的配置骨架 2026/9/26 3:53:27

Codex 结合 better-icons 拿图标:先固定图标库再动手的配置骨架

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
AI Scaffold 项目部署实战:从本地运行到生产环境的 TaoToken 配置指南 2026/9/26 3:53:27

AI Scaffold 项目部署实战:从本地运行到生产环境的 TaoToken 配置指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
如何写出让读者心服口服的“信赖”情感章节?从标题到五幕全拆解 2026/9/26 3:53:21

如何写出让读者心服口服的“信赖”情感章节?从标题到五幕全拆解

看到《第十章 叁年06 莹姐的信赖》这个标题时,我没有急着去猜剧情,而是先把它当成一个写作样本从头到尾拆了一遍。一个真正吸引人的章节标题,往往不只是“剧透”或“标号”,它更像是一份浓缩的叙事方案:位置交代、时间…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉