前言
在JavaScript中,深拷贝是指在拷贝对象的同时,创建一个新的对象,并且递归地复制原对象中的所有嵌套对象和引用类型。这样,新对象和原对象的所有层级都是相互独立的,修改一个对象的属性不会影响另一个对象。在上篇文章中我们讲了浅拷贝,今天我们来聊聊JavaScript中的深拷贝,看看它与浅拷贝有什么不同,并且我们来手搓一个深拷贝源码,在前端面试中容易被问到。
深拷贝和浅拷贝是两种不同的对象复制方式,主要区别在于复制对象时是否递归复制嵌套对象及其引用。
-
浅拷贝:
- 只复制对象的第一层结构,不会递归复制嵌套对象,嵌套对象仍然是原对象和拷贝对象共享的。
- 浅拷贝方法包括 Object.assign、数组的 slice、concat 等。
- 修改拷贝对象的第一层属性不会影响原对象,但修改嵌套对象的属性会影响原对象和拷贝对象。
示例代码:
jslet obj = { a: 1, b: { c: 2 } }; let shallowCopy = Object.assign({}, obj); console.log(shallowCopy); // { a: 1, b: { c: 2 } } shallowCopy.b.c = 3; console.log(obj.b.c); // 3
-
深拷贝:
-
递归复制整个对象结构,包括嵌套对象及其引用,生成一个完全独立的新对象,修改拷贝对象不会影响原对象。
-
深拷贝的实现通常使用递归和循环来处理对象的每一层,防止循环引用导致的无限递归。
-
深拷贝的实现可能相对复杂,但可以确保生成的对象与原对象完全独立。
-
在JS中,因为浅拷贝只复制对象的第一层结构,而不会递归复制嵌套对象并且还是引用嵌套对象,当我们修改嵌套对象中的属性值时,例如此题中的shallowCopy.b.c = 3
,修改新对象时,原对象中的属性值还是会改变。
而深拷贝递归复制整个对象结构,包括嵌套对象及其引用,生成一个完全独立的新对象,也就是说,如果原对象的对象里面还有嵌套的对象,但是深拷贝都会将嵌套的对象复制过去,而不是像浅拷贝一样去引用嵌套的对象,假设shallowCopy
是我们深拷贝obj
创造出的对象,当我们像这样shallowCopy.b.c = 3
去修改对象中嵌套的对象的属性时,对应的对象还是不会改变,这就是深拷贝。
深拷贝实现
通过上篇文章,我们知道了常见的实现浅拷贝的方法,如slice concat assign
等等,那js有没有提供实现深拷贝的方法呢?
其实js是有提供实现深拷贝的方法的,那么就是JSON.parse(JSON.stringify(obj))
,在讲这个方法之前,我们先来搞懂JSON.stringify
和 JSON.parse
。
JSON.stringify
和 JSON.parse
是 JavaScript 中用于处理 JSON 数据的两个核心方法。
-
JSON.stringify:
-
作用:将 JavaScript 对象或值转换为 JSON 字符串。
-
语法:
JSON.stringify(value[, replacer[, space]])
-
参数:
value
: 要转换为 JSON 字符串的值。replacer
(可选):用于转换结果的函数或数组。space
(可选):用于控制缩进的字符串或数字。
-
返回值:表示给定值的 JSON 字符串。
jslet obj = { name: 'John', age: 30 }; let jsonString = JSON.stringify(obj); console.log(jsonString); // '{"name":"John","age":30}'
-
-
JSON.parse:
-
作用:将 JSON 字符串解析为 JavaScript 对象。
-
语法:
JSON.parse(text[, reviver])
-
参数:
text
: 要解析的 JSON 字符串。reviver
(可选):用于在解析期间对结果执行转换的函数。
-
返回值:解析得到的 JavaScript 对象。
jslet jsonString = '{"name":"John","age":30}'; let parsedObj = JSON.parse(jsonString); console.log(parsedObj); // { name: 'John', age: 30 }
-
JSON.parse(JSON.stringify(obj))
使用 JSON.parse(JSON.stringify(obj))
进行深拷贝是一种常见的简单方法。这方法适用于对象中只包含原始类型数据(如字符串、数字、布尔值、null)以及没有循环引用的情况。这种方法的优点是简单易懂,缺点是无法处理包含函数、正则表达式、undefined
等特殊类型的对象。
示例代码:
js
let obj = { a: 1, b: { c: 2 } };
let deepCopyObj = JSON.parse(JSON.stringify(obj));
console.log(deepCopyObj); // { a: 1, b: { c: 2 } }
deepCopyObj.b.c = 3;
console.log(obj.b.c); // 2
需要注意的是:
- 无法处理特殊类型: JSON.stringify() 会忽略对象属性中的函数、正则表达式、undefined 等,因为这些类型在 JSON 中没有对应的表示。在还原时,这些特殊类型将被忽略或转为 null。
- 循环引用问题: 如果对象存在循环引用,即对象属性之间形成一个循环,JSON.stringify() 无法处理,会抛出错误。
手搓源码
js
function deepCopy(obj) {
let objCopy = {}
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
if (obj[key] instanceof Object) { // obj[key]是引用类型
objCopy[key] = deepCopy(obj[key])
} else {
objCopy[key] = obj[key]
}
}
}
return objCopy
}
deepCopy
函数接收一个对象obj
作为参数。let objCopy = {}
创建了一个新的空对象,用于存储深拷贝的结果。for (let key in obj)
遍历对象obj
的所有属性。if (obj.hasOwnProperty(key))
判断属性是否是对象自身的属性,而不是继承自原型链上的属性。if (obj[key] instanceof Object)
判断属性值是否是引用类型(Object 类型)。如果是引用类型,就递归调用deepCopy
对其进行深拷贝,然后将拷贝结果赋值给objCopy[key]
。- 如果属性值不是引用类型,直接将其赋值给
objCopy[key]
。 - 最终返回深拷贝的结果
objCopy
。
总结
1.深拷贝:
- 递归复制整个对象结构,包括嵌套对象及其引用,生成一个完全独立的新对象,修改拷贝对象不会影响原对象。 - 深拷贝的实现通常使用递归和循环来处理对象的每一层,防止循环引用导致的无限递归。 - 深拷贝的实现可能相对复杂,但可以确保生成的对象与原对象完全独立。
-
浅拷贝:
- 只复制对象的第一层结构,不会递归复制嵌套对象,嵌套对象仍然是原对象和拷贝对象共享的。
- 浅拷贝方法包括 Object.assign、数组的 slice、concat 等。
- 修改拷贝对象的第一层属性不会影响原对象,但修改嵌套对象的属性会影响原对象和拷贝对象。