Hi,这里是JustHappy,深拷贝是开发中常用的一个操作,但是方法不止一种,各有利弊,我们来整理整理吧
最简单的展开运算符
对于简单的只有单层的复杂数据类型,我们可以通过展开运算符来处理,这种方式只有一行代码
-
优点:
- 代码简洁,易于理解。
- 性能较好,适合简单对象的拷贝。
-
缺点:
- 仅适用于对象的第一层属性,无法实现真正的深拷贝。如果对象的属性值是另一个对象或数组,展开运算符只会拷贝引用,不会递归拷贝内部结构。
- 不能处理特殊数据类型(如
Date、RegExp、Map、Set等)。
-
例子:
// 拷贝对象1:单层的数据
const obj1 = {
name: "JustHappy",
age: 22,
sex: "male",
};
/**
* 对于单层的数据,可以直接使用展开运算符进行拷贝
*/
const copy1 = { ...obj1 };
console.log(copy1 === obj1); // false 深拷贝成功
当然,如果你使用展开运算符去拷贝多层嵌套的对象,打印出来你会发现也一样,但是从第二层开始的就不是深拷贝了。而是浅拷贝,如下
// 拷贝对象2:多层的数据
const obj1 = {
name: "JustHappy",
age: 22,
sex: "male",
friend: {
name: "Tom"
}
};
const copy1 = { ...obj1 };
console.log(copy1.friend === obj1.friend); // true 这里的friend是浅拷贝
JSON.stringify + JSON.parse
通过将对象序列化为 JSON 字符串,再反序列化为新的对象,从而实现深拷贝。这个方式相比展开运算符是可以可以处理嵌套对象的
-
优点:
- 实现简单,代码量少。
- 可以处理嵌套对象。
-
缺点:
- 不能处理特殊数据类型(如
Date、RegExp、Function、Map、Set等),这些类型会被忽略或转换为其他形式。 - 无法拷贝对象的原型链。
- 性能较差,尤其是对于复杂对象,因为涉及到字符串序列化和反序列化。
- 不能处理特殊数据类型(如
-
例子:
// 拷贝对象2:多层的数据
const obj1 = {
name: "JustHappy",
age: 18,
sex: "male",
friends: [
{
name: "Tom",
age: 18,
sex: "male",
},
{
name: "Jerry",
age: 18,
sex: "male",
},
],
};
const copy1 = JSON.parse(JSON.stringify(obj1));
console.log(copy1 === obj1); // false
console.log(copy1.friends === obj1.friends); // false
递归深拷贝
通过递归遍历对象的每个属性,逐层拷贝其值。如果属性值是对象或数组,则递归调用拷贝函数
-
优点:
- 以实现真正的深拷贝。
- 可以自定义处理特殊数据类型(如
Date、Map、Set等)。
-
缺点:
- 代码较为复杂,需要手动处理各种数据类型。
- 如果对象结构过深,可能会导致调用栈溢出。
-
例子:
// 拷贝对象2:多层的数据
const obj1 = {
name: "JustHappy",
age: 18,
sex: "male",
friends: [
{
name: "Tom",
age: 18,
sex: "male",
},
{
name: "Jerry",
age: 18,
sex: "male",
},
],
};
function deepClone(obj) {
if (typeof obj !== "object" || obj === null) return obj;
let result;
// 判断是数组还是对象还是其他的
if (Array.isArray(obj)) {
result = [];
} else if (typeof obj === "object") {
result = {};
}
for (let key in obj) {
// 保证 key 不是原型上的属性:这样避免死循环
if (obj.hasOwnProperty(key)) {
result[key] = deepClone(obj[key]);
}
}
return result;
}
const copy2 = deepClone(obj1);
console.log(copy2 === obj1); // false
console.log(copy2.friends === obj1.friends); // false
如果你需要识别并深拷贝 Date、Map、Set 这些,只需要加些逻辑就好了。就像下面这样
function deepClone(obj) {
if (typeof obj !== "object" || obj === null) return obj;
let result;
if (Array.isArray(obj)) {
result = [];
} else if (typeof obj === "object") {
result = {};
}
// 添加的逻辑
if (obj instanceof Date) return new Date(obj);
if (obj instanceof Map) return new [...Mapobj]();
if (obj instanceof Set) return new Set([...obj]);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
result[key] = deepClone(obj[key]);
}
}
return result;
}
一劳永逸的 structuredClone
对!你是不是烦了,有没有一种方法一劳永逸呢?不用思考那么多呢?
有的!structuredClone 是 ES2023 新增的内置方法,用于实现深拷贝。
-
优点:
- 原生支持,性能较好。
- 可以处理复杂数据类型(如
Date、Map、Set、ArrayBuffer等)。 - 自动处理循环引用。
-
缺点:
- 浏览器兼容性有限(需要检查浏览器支持情况)。
const obj = { a: 1, b: { c: 2 }, d: [3, 4] };
const newObj = structuredClone(obj);
console.log(newObj); // { a: 1, b: { c: 2 }, d: [3, 4] }
console.log(obj.b === newObj.b); // false(b 属性是深拷贝)