在 JavaScript 开发中,我们经常需要复制对象或数组。但你有没有想过,简单的赋值操作背后,隐藏着深拷贝和浅拷贝的秘密?如果理解不透彻,你的代码很可能埋下难以察觉的 Bug。今天,我们就来彻底搞懂 JavaScript 的深拷贝和浅拷贝!
什么是浅拷贝?
浅拷贝,顾名思义,只拷贝对象或数组的“表面”一层。它会创建一个新的对象或数组,但新对象/数组中的属性或元素,如果本身是引用类型(例如对象、数组),则仍然指向原始对象/数组的内存地址。
浅拷贝的特点:
- 只复制第一层属性/元素。
- 如果属性/元素是引用类型,则复制的是引用地址,而不是值本身。
- 修改新对象/数组的引用类型属性/元素,会影响原始对象/数组。
浅拷贝的常见方式:
-
直接赋值(=)
let obj1 = { a: 1, b: { c: 2 } }; let obj2 = obj1; // 浅拷贝 obj2.a = 3; obj2.b.c = 4; console.log(obj1); // 输出:{ a: 3, b: { c: 4 } } console.log(obj2); // 输出:{ a: 3, b: { c: 4 } } -
Object.assign()let obj1 = { a: 1, b: { c: 2 } }; let obj2 = Object.assign({}, obj1); // 浅拷贝 obj2.a = 3; obj2.b.c = 4; console.log(obj1); // 输出:{ a: 1, b: { c: 4 } } console.log(obj2); // 输出:{ a: 3, b: { c: 4 } } -
展开运算符(...)
let obj1 = { a: 1, b: { c: 2 } }; let obj2 = { ...obj1 }; // 浅拷贝 obj2.a = 3; obj2.b.c = 4; console.log(obj1); // 输出:{ a: 1, b: { c: 4 } } console.log(obj2); // 输出:{ a: 3, b: { c: 4 } } -
数组的
slice()和concat()let arr1 = [1, { a: 2 }]; let arr2 = arr1.slice(); // 浅拷贝 // let arr2 = arr1.concat(); // 浅拷贝 arr2[0] = 3; arr2[1].a = 4; console.log(arr1); // 输出:[1, { a: 4 }] console.log(arr2); // 输出:[3, { a: 4 }]
什么是深拷贝?
深拷贝会创建一个全新的对象或数组,新对象/数组中的所有属性或元素,包括引用类型,都会被递归地复制一份新的,而不是指向原始对象的内存地址。
深拷贝的特点:
- 复制所有层级的属性/元素。
- 如果属性/元素是引用类型,则复制的是值本身,而不是引用地址。
- 修改新对象/数组的任何属性/元素,都不会影响原始对象/数组。
深拷贝的常见方式:
-
JSON.parse(JSON.stringify())let obj1 = { a: 1, b: { c: 2 }, d: undefined, e: function() {} }; let obj2 = JSON.parse(JSON.stringify(obj1)); // 深拷贝 obj2.a = 3; obj2.b.c = 4; console.log(obj1); // 输出:{ a: 1, b: { c: 2 }, d: undefined } console.log(obj2); // 输出:{ a: 3, b: { c: 4 } }注意:
- 这种方法虽然简单,但存在一些局限性:
- 无法处理
undefined、function、Symbol等特殊类型,会丢失。 - 无法处理循环引用的对象,会报错。
- 无法处理
Date对象,会变成字符串。
- 无法处理
- 这种方法虽然简单,但存在一些局限性:
-
递归实现深拷贝
function deepClone(obj) { if (typeof obj !== 'object' || obj === null) { return obj; // 如果不是对象或为null,直接返回 } const clonedObj = Array.isArray(obj) ? [] : {}; // 创建新对象或数组 for (let key in obj) { if (obj.hasOwnProperty(key)) { clonedObj[key] = deepClone(obj[key]); // 递归调用 } } return clonedObj; } let obj1 = { a: 1, b: { c: 2 } }; let obj2 = deepClone(obj1); // 深拷贝 obj2.a = 3; obj2.b.c = 4; console.log(obj1); // 输出:{ a: 1, b: { c: 2 } } console.log(obj2); // 输出:{ a: 3, b: { c: 4 } }注意:
- 这种方法可以处理循环引用,但需要注意性能问题,对于大型对象可能会比较慢。
总结:
- 浅拷贝只复制对象/数组的第一层,引用类型属性/元素仍然指向原始对象/数组的内存地址。
- 深拷贝会递归复制所有层级的属性/元素,包括引用类型,创建全新的对象/数组。
- 选择浅拷贝还是深拷贝,取决于你的具体需求。
-
JSON.parse(JSON.stringify())简单但有局限性。 - 递归实现深拷贝可以处理循环引用,但要注意性能。
结尾:
掌握深拷贝和浅拷贝是 JavaScript 开发的基本功。希望通过这篇文章,你能够彻底理解它们,并在实际开发中灵活运用,避免不必要的 Bug。如果你觉得这篇文章对你有帮助,请分享给你的朋友们吧!