JavaScript 对象复制:浅拷贝与深拷贝使用说明
在 JavaScript 中,对象复制分为 浅拷贝 和 深拷贝,两者的核心区别在于是否递归复制嵌套的引用类型属性。以下是详细说明和示例:
一、浅拷贝(Shallow Copy)
特点:仅复制对象的第一层属性,若属性是引用类型(如对象、数组),则拷贝的是引用地址。修改嵌套的引用属性会影响原对象。
常用方法:
-
Object.assign()
javascript
const obj = { a: 1, b: { c: 2 } }; const shallowCopy = Object.assign({}, obj); shallowCopy.b.c = 99; // 原对象的 obj.b.c 也会变为 9 或者 const shallowCopy = {}; Object.assign(shallowCopy, obj); 也可以复制 reactive 或 ref 创建的响应式对象 const refObj = ref({}); Object.assign(refObj.value, proxyObj);
-
展开运算符
...
javascript
const obj = { a: 1, b: { c: 2 } }; const shallowCopy = { ...obj };
-
数组浅拷贝
javascript
const arr = [1, 2, { d: 3 }]; const newArr = arr.slice(); // 或 [...arr], Array.from(arr)
适用场景:
-
对象属性无嵌套引用类型。
-
需要快速复制且性能敏感的场景。
二、深拷贝(Deep Copy)
特点:完全复制对象及其所有嵌套属性,新旧对象完全独立,互不影响。
常用方法:
-
JSON.parse(JSON.stringify())
javascript
const obj = { a: 1, b: { c: 2 }, d: new Date() }; const deepCopy = JSON.parse(JSON.stringify(obj)); 如果是 reactive 或 ref 创建的响应式对象,先通过 toRaw 转为 普通对象,再进行拷贝。 例如: const deepCopy = JSON.parse(JSON.stringify(toRaw(proxyObj))); // deepCopy 为普通对象 const deepCopy = reactive(JSON.parse(JSON.stringify(toRaw(proxyObj)))); // deepCopy 为 reactive 响应式对象 Proxy(Object) const deepCopy = ref(JSON.parse(JSON.stringify(toRaw(proxyObj)))); // deepCopy 为ref响应式对象 RefImpl
局限性:
-
忽略
undefined
、函数、Symbol。 -
破坏特殊对象(如 Date 转为字符串,RegExp 转为空对象)。
-
无法处理循环引用。
-
-
structuredClone()
(现代浏览器/Node.js 17+)javascript
const obj = { a: 1, b: { c: 2 } }; const deepCopy = structuredClone(obj); // 支持更多类型,如 Date、Set、Map 如果是 reactive 或 ref 创建的响应式对象,先通过 toRaw 转为 普通对象,再进行拷贝。 例如: const deepCopy = structuredClone(toRaw(proxyObj)); // deepCopy 为普通对象 const deepCopy = reactive(structuredClone(toRaw(proxyObj))); // deepCopy 为 reactive 响应式对象 Proxy(Object) const deepCopy = ref(structuredClone(toRaw(proxyObj))); // deepCopy 为ref响应式对象 RefImpl
-
Lodash 的
cloneDeep()
javascript
import { cloneDeep } from 'lodash'; const obj = { a: 1, b: { c: 2 } }; const deepCopy = cloneDeep(obj); // 处理复杂对象(函数、循环引用等)
-
手动递归实现
javascript
function deepClone(source, map = new WeakMap()) { if (source === null || typeof source !== 'object') return source; if (map.has(source)) return map.get(source); // 解决循环引用 const target = Array.isArray(source) ? [] : {}; map.set(source, target); for (const key in source) { if (source.hasOwnProperty(key)) { target[key] = deepClone(source[key], map); } } return target; }
适用场景:
-
对象包含多层嵌套引用。
-
需要完全独立副本(如状态管理、撤销操作)。
三、如何选择?
方法 | 类型 | 优点 | 缺点 |
---|---|---|---|
Object.assign | 浅拷贝 | 简单快速 | 无法处理嵌套引用 |
JSON 序列化 | 深拷贝 | 原生支持 | 丢失特殊类型,无法处理循环引用 |
structuredClone | 深拷贝 | 支持更多类型 | 兼容性要求(IE 不支持) |
Lodash cloneDeep | 深拷贝 | 功能全面 | 需引入第三方库 |
四、注意事项
-
循环引用:手动实现或第三方库需处理对象间的循环引用。
-
特殊类型:根据数据类型选择方法(如
structuredClone
支持 Date、Map 等)。 -
性能:深拷贝对大型对象有较高开销,优先考虑浅拷贝。
总结
-
浅拷贝:用
Object.assign
或...
,适合简单对象。 -
深拷贝:优先使用
structuredClone
或 Lodash,复杂场景手动实现需谨慎。
通过合理选择拷贝方式,可避免数据意外修改,提升代码健壮性。