探究JavaScript中的深拷贝:细节与实现
探究JavaScript中的深拷贝:细节与实现
JavaScript作为一种动态的、弱类型的语言,处理数据结构时有其独特的挑战。深拷贝是其中的一个经常讨论的话题。在本文中,我们将深入探讨深拷贝的实现,并为其提供一个可靠的函数。
什么是深拷贝?
简而言之,深拷贝是对一个对象的复制,这意味着复制的对象与原对象在内存中是完全独立的。修改一个对象不会影响另一个对象。
如何实现深拷贝?
以下是我们提供的一个deepClone
函数实现:
function deepClone(target) {
// 创建一个 WeakMap 来保存已经拷贝过的对象,以防止循环引用
const map = new Map();
// 辅助函数:判断一个值是否为对象或函数
function isObject(target) {
return (
(typeof target === "object" && target) || // 检查是否是非null的对象
typeof target === "function" // 或者是函数
);
}
// 主要的拷贝函数
function clone(data) {
// 基本类型直接返回
if (!isObject(data)) {
return data;
}
// 对于日期和正则对象,直接使用它们的构造函数创建新的实例
if ([Date, RegExp].includes(data.constructor)) {
return new data.constructor(data);
}
// 对于函数,创建一个新函数并返回
if (typeof data === "function") {
return new Function("return " + data.toString())();
}
// 检查该对象是否已被拷贝过
const exist = map.get(data);
if (exist) {
return exist; // 如果已经拷贝过,直接返回之前的拷贝结果
}
// 如果数据是 Map 类型
if (data instanceof Map) {
const result = new Map();
map.set(data, result); // 记录当前对象到 map
data.forEach((val, key) => {
// 对 Map 的每一个值进行深拷贝
result.set(key, clone(val));
});
return result; // 返回新的 Map
}
// 如果数据是 Set 类型
if (data instanceof Set) {
const result = new Set();
map.set(data, result); // 记录当前对象到 map
data.forEach((val) => {
// 对 Set 的每一个值进行深拷贝
result.add(clone(val));
});
return result; // 返回新的 Set
}
// 获取对象的所有属性,包括 Symbol 类型和不可枚举的属性
const keys = Reflect.ownKeys(data);
// 获取对象所有属性的描述符
const allDesc = Object.getOwnPropertyDescriptors(data);
// 创建新的对象并继承原对象的原型链
const result = Object.create(Object.getPrototypeOf(data), allDesc);
map.set(data, result); // 记录当前对象到 map
// 对象属性的深拷贝
keys.forEach((key) => {
result[key] = clone(data[key]);
});
return result; // 返回新的对象
}
return clone(target); // 开始深拷贝
}
// 测试的sample对象
const sample = {
// =========== 1.基础数据类型 ===========
numberVal: 123,
stringVal: "OpenAI",
booleanVal: false,
undef: undefined,
nil: null,
symKey: Symbol("key"),
bigNumber: BigInt(1234567890n),
// =========== 2.Object类型 ===========
// 普通对象
user: {
firstName: "John",
lastName: "Doe",
},
// 数组
list: ["apple", "banana", "cherry"],
// 函数
display: function() {
console.log("This is a display function");
},
// 日期
birthDate: new Date(2000, 0, 1),
// 正则
pattern: new RegExp("/pattern/g"),
// Map
translations: new Map().set("hello", "hola"),
// Set
tags: new Set().add("fruit").add("food"),
// =========== 3.其他 ===========
[Symbol("unique")]: "uniqueValue",
};
// 4.添加不可枚举属性
Object.defineProperty(sample, "hidden", {
enumerable: false,
value: "Hidden Property",
});
// 5.设置原型对象
Object.setPrototypeOf(sample, {
prototypeKey: "prototypeValue",
});
// 6.设置loop成循环引用的属性
sample.self = sample;
测试结果:

关键部分解析
-
使用Map来避免循环引用: 在JavaScript中,对象可以包含对其他对象的引用,这可能会创建一个循环。为了避免无限的递归调用,我们使用了一个
Map
来保存已经被拷贝过的对象。 -
isObject辅助函数: 这个函数帮助我们判断一个值是否是一个对象或函数,这是因为在JavaScript中,函数也是对象。
-
处理特殊对象: 日期和正则表达式对象有特殊的构造函数,我们可以直接用它们的构造函数创建新的实例。
-
处理函数: 我们可以通过使用
Function
构造函数来复制一个函数。 -
处理Map和Set: 对于
Map
和Set
这两种数据结构,我们需要遍历它们的每一个值,并进行深拷贝。 -
处理对象的所有属性: 使用
Reflect.ownKeys
可以帮助我们获取对象的所有属性(包括Symbol
类型和不可枚举的属性),然后对这些属性进行深拷贝。
结论
深拷贝在JavaScript中是一个经常需要面对的挑战。我们的deepClone
函数提供了一个可靠的方法来复制几乎所有类型的JavaScript对象。希望这篇文章能帮助您更好地理解深拷贝及其在JavaScript中的实现。
转载自:https://juejin.cn/post/7274856158174298166