浅拷贝只复制第一层属性,嵌套引用仍共享;深拷贝递归复制所有层级,完全隔离。本质源于基本类型存栈、引用类型存堆,赋值即传地址,拷贝方式决定是否影响原数据。

JS 中浅拷贝与深拷贝的核心,不在“怎么写”,而在“为什么必须分清”——它直接决定你改的是副本,还是悄悄动了原始数据的命脉。
先看本质区别:栈和堆在说话
JavaScript 变量里存的不是“数据本身”,而是“数据在哪”。
- 基本类型(string/number/boolean/null/undefined/Symbol/BigInt):值存在栈里,赋值就是真复制一份,互不干扰。
- 引用类型(Object、Array、Date、RegExp、Map、Set 等):变量存的是堆内存地址,赋值只是把钥匙交出去——A 和 B 拿着同一把钥匙,开同一扇门。
所谓“拷贝”,其实是问:你给的是新钥匙(浅拷贝),还是连房子都重盖一套(深拷贝)?
浅拷贝:只换门,不拆墙
它创建一个新对象,但只复制第一层属性。如果某属性是对象或数组,拷贝进去的仍是原地址。
- 常见方法:
Object.assign({}, obj)、{...obj}、arr.slice()、[...arr]、Array.from(arr) - 典型翻车场景:修改
copy.user.profile.name,original.user.profile.name也变了——因为profile是共享引用。 - 关键提醒:它不处理原型链、不可枚举属性、Symbol 键,也不进第二层。
深拷贝:从地基到装修全重来
递归遍历所有层级,对每个引用类型都新开一块内存,确保完全隔离。
-
JSON 方案:
JSON.parse(JSON.stringify(obj))—— 快但残缺:丢函数、undefined、Symbol、Date 变字符串、RegExp 变空对象、循环引用直接报错。 -
structuredClone(推荐):现代浏览器原生支持,能正确处理 Map/Set/Date/RegExp/ArrayBuffer/Transferable 对象,且自动识别循环引用。用法简单:
const copy = structuredClone(original)。 - 手写递归:适合需要兼容老环境或定制逻辑(比如过滤某些字段、跳过函数)的场景,需手动处理类型判断、循环引用(WeakMap 缓存)、特殊对象实例化等。
选哪个?看场景,别硬套
没有“最好”,只有“最合适”:
- 纯一维数组或对象 → 展开运算符或
slice()足够,轻快又安全。 - 含嵌套但结构简单、无特殊值 →
structuredClone是首选,健壮且标准。 - 要兼容 IE 或需精细控制 → 手写深拷贝或引入
lodash.cloneDeep。 - 大列表渲染时频繁拷贝 → 先判断是否真需要深拷贝,有时 immutable 更新策略(如用
immer)比全量克隆更高效。











