术语“深拷贝”在 JavaScript 的语言规范中没有正式定义,但在社区中通常被很好地理解。深拷贝会复制 JavaScript 值,从而产生一个完全新的值,该值没有任何引用指向原始对象中的属性(如果它是一个对象)。对深拷贝对象所做的任何更改都不会影响原始对象。
实现一个 deepClone
函数,该函数对 JavaScript 对象执行深拷贝操作。您可以假设输入只包含 JSON 可序列化的值(null
、boolean
、number
、string
、Array
、Object
),并且不包含任何其他对象,如 Date
、Regex
、Map
或 Set
。
const obj1 = { user: { role: 'admin' } };const clonedObj1 = deepClone(obj1);clonedObj1.user.role = 'guest'; // 将克隆的用户角色更改为 'guest'。clonedObj1.user.role; // 'guest'obj1.user.role; // 应该仍然是 'admin'。const obj2 = { foo: [{ bar: 'baz' }] };const clonedObj2 = deepClone(obj2);obj2.foo[0].bar = 'bax'; // 修改原始对象。obj2.foo[0].bar; // 'bax'clonedObj2.foo[0].bar; // 应该仍然是 'baz'。
术语“深拷贝”在 JavaScript 的语言规范中没有正式定义,但在社区中通常被很好地理解。深拷贝会复制 JavaScript 值,从而产生一个完全新的值,该值没有任何引用指向原始对象中的属性(如果它是一个对象)。对深拷贝对象所做的任何更改都不会影响原始对象。
实现一个 deepClone
函数,该函数对 JavaScript 对象执行深拷贝操作。您可以假设输入只包含 JSON 可序列化的值(null
、boolean
、number
、string
、Array
、Object
),并且不包含任何其他对象,如 Date
、Regex
、Map
或 Set
。
const obj1 = { user: { role: 'admin' } };const clonedObj1 = deepClone(obj1);clonedObj1.user.role = 'guest'; // 将克隆的用户角色更改为 'guest'。clonedObj1.user.role; // 'guest'obj1.user.role; // 应该仍然是 'admin'。const obj2 = { foo: [{ bar: 'baz' }] };const clonedObj2 = deepClone(obj2);obj2.foo[0].bar = 'bax'; // 修改原始对象。obj2.foo[0].bar; // 'bax'clonedObj2.foo[0].bar; // 应该仍然是 'baz'。
在典型的面试约束下,从头开始编写一个完整的深拷贝解决方案几乎是不可能的。在典型的面试环境中,范围相当有限,面试官更感兴趣的是您将如何检测不同的数据类型以及您利用各种内置 API 和 Object
方法遍历给定对象的能力。
JSON.stringify
在 JavaScript 中深度复制对象的最简单(但有缺陷)的方法是首先对其进行序列化,然后通过 JSON.stringify
和 JSON.parse
将其反序列化。
export default function deepClone(value) {return JSON.parse(JSON.stringify(value));}
尽管这种方法在给定的输入对象仅包含 null
、boolean
、number
、string
时是可以接受的,但您应该意识到这种方法的缺点:
JSON.stringify
还有一些令人惊讶的行为,例如将 Date
对象转换为 ISO 时间戳字符串,NaN
和 Infinity
变为 null
等。显然,您的面试官不会允许您使用它。
这是一个不依赖于 JSON.stringify
和 JSON.parse
的解决方案。
/*** @template T* @param {T} value* @return {T}*/export default function deepClone(value) {if (typeof value !== 'object' || value === null) {return value;}if (Array.isArray(value)) {return value.map((item) => deepClone(item));}return Object.fromEntries(Object.entries(value).map(([key, value]) => [key, deepClone(value)]),);}
通常有两种方法可以遍历一个对象:
for ... in
语句循环遍历键。Object.keys()
将对象转换为键数组,或使用 Object.entries()
转换为键值元组数组。使用 for ... in
语句,也会处理继承的可枚举属性。另一方面,Object.keys()
和 Object.entries()
只关心直接在对象上定义的属性,这通常是我们想要的。
我们将在 Deep Clone II 中解决这些边缘情况。
截至撰写本文时,所有主流浏览器都原生支持通过 structuredClone
API 执行深拷贝。如果您想了解更多关于 structuredClone
的特性和限制,请查看 web.dev 上的“使用 structuredClone 在 JavaScript 中进行深拷贝”。
const clonedObj = structuredClone(obj);
console.log()
语句将显示在此处。