学完这篇你能分清浅拷贝和深拷贝的差别,并知道 Object.assign、展开运算符、JSON、structuredClone、手写递归这几种写法各自适合什么场景、会在哪里出问题。
先搞清楚区别:第一层之后还共不共享
浅拷贝只复制第一层属性,嵌套的对象/数组仍然和新对象指向同一份内存;深拷贝则递归复制到底,新旧对象完全独立。
const a = { list: [1, 2] };
const b = Object.assign({}, a); // 浅拷贝
b.list.push(3);
console.log(a.list); // [1, 2, 3],被改到了
判断标准很简单:改新对象里嵌套的那层,老对象会不会跟着变。
第一步:浅拷贝的几种写法
1. Object.assign({}, obj)
复制自身可枚举属性(含 Symbol),遇到 getter 会执行取值。目标是空对象时就是标准浅拷贝。
2. 展开运算符 { ...obj } / [ ...arr ]
写起来最短,行为与 Object.assign 基本一致,现在更常用。
3. 数组专属:arr.slice()、arr.concat()
老代码里常见,等价于数组的浅拷贝。
注意:这三者都只复制“自身可枚举”属性,原型上的属性、不可枚举属性(比如用 defineProperty 设成 enumerable: false 的)都会丢。另外它们都是浅拷贝,嵌套结构照样共享。
第二步:深拷贝的几种写法
1. JSON.parse(JSON.stringify(obj))
最省事,但坑最多:
undefined、函数、Symbol 会被直接丢掉(对象里)或变成 null(数组里)
Date 变成字符串,RegExp、Map、Set 变成空对象 {}
NaN、Infinity 变成 null
- 循环引用直接抛错
- 原型链丢失,类实例变普通对象
只适合“纯数据、无循环引用、无特殊类型”的场景,比如接口返回的纯 JSON。
2. structuredClone(obj)
浏览器和 Node 17+ 原生支持,是目前最推荐的通用方案:
- 支持循环引用、
Date、RegExp、Map、Set、ArrayBuffer、Blob
- 不支持函数、Symbol、DOM 节点,类实例会退化成普通对象(原型丢失)
const copy = structuredClone(obj);
注意:如果你的项目还要兼容老浏览器,用之前查一下目标环境的支持情况;拿不准就用 lodash 兜底。
3. 手写递归
需要保留原型或处理特殊类型时只能自己写,关键是三件事:用 WeakMap 记录已拷贝对象解决循环引用、按 Object.prototype.toString 分类型处理、用 Object.create(Object.getPrototypeOf(obj)) 保住原型。
function deepClone(obj, map = new WeakMap()) {
if (obj === null || typeof obj !== 'object') return obj;
if (map.has(obj)) return map.get(obj);
const target = Array.isArray(obj)
? []
: Object.create(Object.getPrototypeOf(obj));
map.set(obj, target);
for (const key of Reflect.ownKeys(obj)) {
target[key] = deepClone(obj[key], map);
}
return target;
}
4. lodash 的 _.cloneDeep
不想自己维护就装 lodash,边界情况处理得比较全,代价是多一个依赖。
第三步:怎么选
- 只改第一层 → 展开运算符,够用又快
- 纯 JSON 数据、图省事 → JSON 方法(清楚它的丢失规则)
- 现代环境、类型复杂 →
structuredClone
- 要保原型、要精细控制 → 手写递归或 lodash
注意:深拷贝不是越多越好,大对象递归会有性能开销。组件间传数据时优先考虑不可变更新(只重建变化的那一层),比无脑深拷贝更划算。
小结
- 浅拷贝:
{...obj}、Object.assign、slice/concat,只复制第一层,嵌套仍共享
- JSON 深拷贝:丢函数/undefined/Symbol,Date 变字符串,循环引用报错
structuredClone:原生、支持循环引用和多数内置类型,但不支持函数和 DOM
- 手写递归三个要点:WeakMap 防循环、分类型处理、
Object.create 保原型
- 选型的核心是数据长什么样,而不是哪个方法“最强”