深拷贝和浅拷贝
约 1551 字大约 5 分钟
2025-03-11
一、什么是深拷贝和浅拷贝
1、浅拷贝
浅拷贝的新对象与原始对象共享相同的内存地址,因此对其中一个对象进行更改会影响到另一个对象。浅拷贝仅复制对象的第一层结构,而不会递归复制嵌套的对象或数据。
主要应用: 对象的一些属性进行复杂的处理,可以抽离出来;数据缓存,避免重复获取数据。
2、深拷贝
深拷贝的新对象与原始对象具有不同的内存地址,因此彼此之间的更改是相互独立的。深拷贝会递归复制所有嵌套的对象或数据,确保整个对象及其子对象都被复制。
主要应用: 数据备份
3、区别
1、内存引用
浅拷贝复制的是对象的引用,深拷贝复制的是对象的值。
2、对象的变化
对浅拷贝的修改会影响原始对象,因为它们共享相同的引用。而对深拷贝的修改不会影响原始对象。
3、嵌套对象或数据的复制
浅拷贝仅复制第一层对象或数据,嵌套的对象或数据仍然是共享的。深拷贝通过递归复制嵌套的对象或数据,确保每个对象都有独立的副本。
二、常见的值操作
1、直接赋值 浅拷贝
直接复制就等于复制其值引用,为浅拷贝。
const originalObj = { name: "John", age: 30 };
const shallowCopyObj = originalObj;
2、展开运算符 ...
浅拷贝
使用扩展运算符可以创建一个对象或数组的浅拷贝副本
const originalObj = { name: "John", age: 30 }; // 深拷贝
const originalObj = { name: "John", age: 30, desc: { skill: "xxx" } }; // 浅拷贝
const copyObj = { ...originalObj };
3、Object.assign()
方法 浅拷贝
使用 Object.assign()
可以将一个或多个源对象的属性复制到目标对象,并返回目标对象的浅拷贝。
- 新对象与源对象共享相同的属性引用,即修改新对象的属性会影响源对象。
- 只能处理对象身上的可枚举属性,无法处理不可枚举属性,也无法处理
getter
、setter
等特殊属性 - 不能处理含有循环引用的对象
- 无法复制原型链上的属性、即继承来的属性
const originalObj = { name: "John", age: 30 }; // 深拷贝
const originalObj = { name: "John", age: 30, desc: { skill: "xxx" } }; // 浅拷贝
const copyObj = Object.assign({}, originalObj);
4、JSON.stringify()
/ JSON.parse()
深拷贝
利用 Json 的序列化和反序列化来实现深拷贝。
这个操作只能复制可序列化的数据,如 Number
、String
、Boolean
、Array
、扁平对象。这意味着有些类型无法被争取复制。
同时某些比较复杂的对象可能会带来比较大的性能开销,特别是对于嵌套较深或者循环嵌套的对象,导致内存溢出。
const originalObj = { name: "John", age: 30 }; // 深拷贝
const deepCopyObj = JSON.parse(JSON.stringify(originalObj));
以下几种类型的数据无法被正确复制
- 正则表达式
RegExp
、Error
:转换完变成空对象{ }
Date
对象:Date
对象会被转换成时间字符串 ISO 格式(2024-12-29T11:41:01.805Z
)。undefined
、Symbol
:转化后直接属性丢失NaN
、Infinity
、-Infinity
:转化后直接值null
- 循环引用:如果对象之间存在循环引用(例如
a.b=b
,b.a = a
),JSON.stringify() 会抛出错误。 - 函数:函数无法被序列化,直接报错。
5、Lodash 的 cloneDeep()
方法 深拷贝
可以使用第三方库提供的深拷贝方法,如 Loadash 的 cloneDeep()
方法,适用于需要快速实现深拷贝且已经使用 Lodash 的项目。如果项目下没有安装 Loadash,需要考虑引入 Loadash 库带来的项目体积增大等问题。
6、自行实现 deepCopy()
循环引用的问题不好处理,需要引入 WeakMap
1. 不涉及循环引用
适用于版本比较老的浏览器。
function deepCopy(source) {
// 如果不是数组或对象
if (
source === null ||
typeof source !== "object" ||
source instanceof Date ||
source instanceof RegExp
) {
return source;
}
// 如果是数组或对象
let copy = Array.isArray(source) ? [] : {};
for (let key in source) {
if (source.hasOwnProperty(key)) {
copy[key] = deepCopy(source[key]);
}
}
return copy;
}
6.2、涉及循环引用
引入 WeakMap,需要兼容 ES6 以上版本的浏览器。
function deepCopy(target, h = new WeakMap()) {
// 1. 判断 target 是否为对象类型(包括数组)
if (typeof target === "object") {
// 2. 检查 WeakMap 中是否已经存在该对象,防止循环引用
if (h.has(target)) return h.get(target);
// 3. 如果是数组,创建一个空数组,否则创建一个空对象(不带原型的对象)
const newTarget = Array.isArray(target) ? [] : {};
// 4. 把当前对象存入 WeakMap,防止后续遇到循环引用
h.set(target, newTarget);
// 5. 遍历对象的所有键,递归拷贝每一个属性
for (const key in target) {
newTarget[key] = deepCopy(target[key], h);
}
// 6. 返回深拷贝的新对象
return newTarget;
} else {
// 7. 如果 target 不是对象(如字符串、数字、布尔值等),直接返回
return target;
}
}
7、structuredClone()
深拷贝新特性
structuredClone()
是 JavaScript 中用于执行 深拷贝 的现代 API,基于结构化克隆算法。它能安全地克隆复杂对象(如循环引用、Date
、Map
、Set
等),替代传统的 JSON.parse()
、JSON.stringify()
方法。
structuredClone()
接受一个参数,即要克隆的对象,返回一个新的深拷贝对象。
const original = { a: 1, b: { c: 2 } };
const cloned = structuredClone(original);
console.log(cloned.b.c); // 输出: 2
console.log(original === cloned); // false(深拷贝)
类型 | 是否支持 | 说明 |
---|---|---|
基本类型(Number、String 等) | ✅ | 直接克隆 |
Object、Array | ✅ | 递归克隆所有属性 |
Date | ✅ | 克隆为新的 Date 对象 |
RegExp | ❌ | 会被转换为空对象 {} |
Map、Set | ✅ | 保持结构,克隆键值对 |
ArrayBuffer | ✅ | 克隆为新的 ArrayBuffer |
Blob、File | ✅ | 浏览器环境下支持 |
Function | ❌ | 抛出 DOMException(无法克隆函数) |
DOM 元素 | ❌ | 抛出错误 |
原型链 | ❌ | 原型指向不变,但是原型上挂载的自定义方法会丢失 |
循环引用 | ✅ | 自动处理循环引用 |
4. Can i use
更新日志
e7112
-1于