JavaScript 中的深浅拷贝: 原理与实现
前言
在开发过程中,我们经常会遇到需要复制一个对象或数组的情况。在 JavaScript 中,我们可以使用浅拷贝或深拷贝来实现复制功能。浅拷贝只会复制对象或数组的第一层属性,如果属性的值还是对象或数组,那么它们之间的引用关系并不会改变。相反,深拷贝会完全复制对象或数组的所有属性,并创建新的引用关系。
在什么情况下需要使用深拷贝呢?通常来说,当我们希望对象或数组的改变不影响原对象或数组时,就需要使用深拷贝。例如,我们可能希望在处理数据的过程中保留原始数据的副本,或者在修改一个对象的属性时不影响原对象。在本文中,我们将介绍 JavaScript 中的深拷贝方法,并给出使用深拷贝的示例代码。
1.深浅拷贝究竟是什么东西???
在 JavaScript 中,对象和数组都是引用类型,它们的值是存储在内存中的地址,而不是实际的值。当我们复制一个对象或数组时,如果我们只是复制它们的地址,那么原来的对象或数组和新的对象或数组都指向同一个地址,对其中一个对象或数组的修改会影响另一个对象或数组。这种复制方式称为浅拷贝。为了避免这种情况,我们需要进行深拷贝,即复制对象或数组的值,而不是复制地址。
2.深拷贝的实现
深拷贝的方法有很多种,常见的方法包括使用 `JSON.parse` 和 `JSON.stringify`、使用递归算法、使用 lodash 等。
2.1 使用JSON.parse
和 JSON.stringify
进行深拷贝
使用 JSON.parse
和 JSON.stringify
进行深拷贝的方法非常简单,只需要先将要拷贝的对象或数组使用 JSON.stringify
转化为字符串,然后再使用 JSON.parse
将字符串转回原来的对象或数组即可。
下面是一个简单的示例,展示了如何使用 JSON.parse
和 JSON.stringify
进行深拷贝:
const original = { a: 1, b: { c: 2 } };
// 使用 JSON.stringify 将对象转化为字符串,再使用 JSON.parse 将字符串转回对象
const copy = JSON.parse(JSON.stringify(original));
console.log(original === copy); // false
console.log(original.b === copy.b); // false
在这个示例中,我们先定义了一个包含嵌套对象的对象 original
,然后使用 JSON.parse
和 JSON.stringify
进行深拷贝,得到了新的对象 copy
。最后,我们使用 ===
运算符比较了两个对象是否相等,以及两个对象的嵌套对象是否相等,发现了两个对象和两个嵌套对象都不相等,说明进行了深拷贝。
2.2 使用递归算法实现深拷贝
使用 `JSON.parse` 和 `JSON.stringify` 进行深拷贝也有一些限制,比如不能复制函数、Symbol 等类型的值,也不能正确地复制循环引用的对象。
因此,在实际应用中,我们还需要考虑使用其他的深拷贝方法来解决这些限制,例如递归算法实现深拷贝,它可以递归地复制对象或数组的值,并判断是否为基本类型(如数字、字符串、布尔值等),如果是基本类型就直接返回,否则就继续递归复制。
下面是一个使用递归算法实现深拷贝的示例:
function deepCopy(obj) {
if (typeof obj !== 'object' || obj === null) {
return obj;
}
// 判断是数组还是对象
const isArray = Array.isArray(obj);
const copy = isArray ? [] : {};
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
copy[key] = deepCopy(obj[key]);
}
}
return copy;
}
const original = { a: 1, b: { c: 2 } };
const copy = deepCopy(original);
console.log(original === copy); // false
console.log(original.b === copy.b); // false
在这个示例中,我们定义了一个名为 deepCopy
的函数,它接受一个对象或数组作为参数,并进行深拷贝。首先,我们使用 typeof
运算符判断传入的参数是否为对象或数组,如果不是就直接返回。然后,我们使用 Array.isArray
函数判断传入的参数是数组还是对象,并创建新的数组或对象。最后,我们使用循环遍历对象或数组的属性,并使用递归调用 deepCopy
函数来复制属性的值。
使用递归算法实现深拷贝的方法相对来说比较复杂,但是它有一个明显的优势,就是可以正确地复制函数、Symbol 等类型的值,以及循环引用的对象。
2.2 使用深拷贝时该注意什么?
- 与原始数据的类型相关的问题
在使用深拷贝时,我们要注意,有些类型的数据在被深拷贝后可能会发生变化。例如,使用 JSON.parse()
和 JSON.stringify()
函数时,会把函数转换成字符串,而使用递归函数时,会把正则表达式转换成空对象。
- 与浅拷贝相关的问题
在某些情况下,我们希望对象或数组的某些属性不被深拷贝,而是使用浅拷贝。这可以通过自定义递归函数来实现。例如,可以设置一个黑名单,表示哪些属性不需要深拷贝。
3. 浅拷贝的实现
浅拷贝是指在复制对象或数组时,只复制它的第一层属性,而不会复制它的属性的值(如果属性的值还是对象或数组的话)。因此,浅拷贝得到的新对象或数组与原对象或数组之间仍然存在引用关系。
在 JavaScript 中,我们可以使用下面几种方法来实现浅拷贝:
- 使用展开运算符(
...
):let newArray = [...oldArray]
,let newObject = {...oldObject}
- 使用
Object.assign()
函数:let newObject = Object.assign({}, oldObject)
- 使用数组的
slice()
函数或对象的Object.keys()
函数:let newArray = oldArray.slice()
,let newObject = Object.keys(oldObject).reduce((acc, key) => ({...acc, [key]: oldObject[key]}), {})
3.1 浅拷贝的应用场景
与深拷贝相比,浅拷贝的实现方法要简单得多,但是它的功能也相对较弱。如果我们希望复制对象或数组的所有属性,并创建新的引用关系,还是需要使用深拷贝,但是在某些情况下,我们可能希望使用浅拷贝。
-
当我们需要构建一个新的对象或数组时,可能希望使用浅拷贝。因为浅拷贝只复制第一层属性,所以可以节省拷贝的时间和空间。例如,我们可以使用浅拷贝来创建一个新的数组,其中包含原数组的所有元素,但是不包含原数组的任何属性。
-
当我们希望复制对象或数组的部分属性时,也可以使用浅拷贝。例如,我们可以使用展开运算符或
Object.assign()
函数来复制对象的某些属性。这可以节省拷贝的时间和空间,因为我们只复制了需要的属性。
总之,浅拷贝在一些特定的场景下非常有用,但是它的功能也相对较弱。我们应该根据实际情况选择使用浅拷贝
3.2 浅拷贝的实现方法
下面我们列举几种常见的方法:
- 使用展开运算符(...):
function shallowClone(obj) {
return {...obj};
}
- 使用
Object.assign()
函数:
function shallowClone(obj) {
return Object.assign({}, obj);
}
- 使用内置构造函数:
function shallowClone(obj) {
if (obj === null || typeof obj !== 'object') {
return obj;
}
let clone = new obj.constructor();
for (let key in obj) {
clone[key] = obj[key];
}
return clone;
}
let obj = {a: 1, b: 2, c: [3, 4, 5]};
let shallow = shallowClone(obj);
console.log(shallow); // {a: 1, b: 2, c: [3, 4, 5]}
4.总结
最后,在使用深浅拷贝时,我们要根据实际的需求来决定使用哪种方法,并确保复制的对象或数组能够满足我们的需求。
如有帮助,麻烦点个赞,如有错误请指出,我是CoderBug,一个跟你一样追风的少年!
转载自:https://juejin.cn/post/7182593543267942457