JavaScript深拷贝和浅拷贝
在JavaScript中,深拷贝和浅拷贝是处理对象或数组时的两个重要概念。它们决定了当你复制一个复杂数据类型时,修改副本是否会影响原始对象。
浅拷贝
浅拷贝是指创建一个新的对象,但新对象的属性是对原始对象属性的引用。如果原始对象的属性是基本数据类型,拷贝的是值;如果是引用类型,拷贝的是地址。
示例代码
const original = { name: "Alice", address: { city: "Wonderland" } };
const shallowCopy = { ...original };
// 修改拷贝的顶层属性
shallowCopy.name = "Bob";
console.log(original.name); // Alice
// 修改嵌套对象
shallowCopy.address.city = "Neverland";
console.log(original.address.city); // Neverland
常见浅拷贝实现方法
-
Object.assign()const shallowCopy = Object.assign({}, original); -
扩展运算符
...const shallowCopy = { ...original }; -
数组的
Array.prototype.slice()或concat()const shallowCopy = originalArray.slice(); const shallowCopy = originalArray.concat();
深拷贝
深拷贝是指创建一个新的对象,并递归地复制原始对象的所有属性,包括嵌套的引用类型。深拷贝后,副本与原始对象完全独立,任何一方的修改都不会影响另一方。
示例代码
const original = { name: "Alice", address: { city: "Wonderland" } };
const deepCopy = JSON.parse(JSON.stringify(original));
// 修改深拷贝的嵌套属性
deepCopy.address.city = "Neverland";
console.log(original.address.city); // Wonderland
常见深拷贝实现方法
-
JSON.parse(JSON.stringify())-
简单易用,但有局限性:
- 忽略函数、
undefined、Symbol属性。 - 不支持循环引用。
const deepCopy = JSON.parse(JSON.stringify(original)); - 忽略函数、
-
-
手写递归函数
-
对复杂对象和循环引用支持较好。
function deepClone(obj, hash = new WeakMap()) { if (obj == null || typeof obj !== "object") return obj; if (hash.has(obj)) return hash.get(obj); // 处理循环引用 const copy = Array.isArray(obj) ? [] : {}; hash.set(obj, copy); for (const key in obj) { if (obj.hasOwnProperty(key)) { copy[key] = deepClone(obj[key], hash); } } return copy; }
-
-
使用第三方库
-
如 Lodash的cloneDeep
import _ from "lodash"; const deepCopy = _.cloneDeep(original);
-
对比浅拷贝和深拷贝
| 特性 | 浅拷贝 | 深拷贝 |
|---|---|---|
| 复制级别 | 仅复制第一层 | 递归复制所有层级 |
| 引用类型处理 | 复制引用地址 | 创建独立的副本 |
| 性能 | 通常性能较优 | 通常性能较低 |
| 使用场景 | 适用于浅层结构或不变嵌套数据 | 适用于复杂对象或避免原始数据污染 |
选择合适的拷贝方式
- 如果数据结构较浅或无需深层次独立,使用浅拷贝(如
Object.assign或...)。 - 如果数据结构复杂或要求副本完全独立,使用深拷贝(如
JSON.parse(JSON.stringify)或_.cloneDeep)。
常见面试题
-
如何实现一个深拷贝函数?
- 考查对递归和循环引用的处理。
function deepClone(obj, hash = new WeakMap()) { if (obj == null || typeof obj !== "object") return obj; if (hash.has(obj)) return hash.get(obj); // 处理循环引用 const copy = Array.isArray(obj) ? [] : {}; hash.set(obj, copy); for (const key in obj) { if (obj.hasOwnProperty(key)) { copy[key] = deepClone(obj[key], hash); } } return copy; } -
JSON.parse(JSON.stringify()为什么不总是可靠?-
忽略
undefined和函数,不能处理循环引用。 -
示例:
const obj = { a: undefined, b: () => {}, c: Symbol("test") }; console.log(JSON.parse(JSON.stringify(obj))); // { }
-
-
如何区分深拷贝和浅拷贝?
-
给定一个嵌套对象,修改副本的嵌套属性,看原始对象是否受影响。
-
示例:
const original = { a: { b: 1 } }; const shallowCopy = { ...original }; shallowCopy.a.b = 2; console.log(original.a.b); // 2 const deepCopy = JSON.parse(JSON.stringify(original)); deepCopy.a.b = 3; console.log(original.a.b); // 1
-
-
第三方库(如 Lodash)的
_.cloneDeep有哪些优点?-
支持循环引用和多种数据类型。
-
示例:
const obj = { a: 1 }; obj.self = obj; const deepCopy = _.cloneDeep(obj); console.log(deepCopy.self === deepCopy); // true
-
-
性能优化:何时选择浅拷贝而非深拷贝?
-
如果对象结构简单且无需深度独立,浅拷贝性能更优。
-
示例:
const arr = [1, 2, 3]; const shallowCopy = arr.slice();
-