深拷贝(Deep Copy)和浅拷贝(Shallow Copy)是在 JavaScript 中常用的两种对象复制方法。
浅拷贝:
- 浅拷贝是指将源对象的属性值复制到新对象中,如果属性值是基本类型(如字符串、数字、布尔值等),则直接复制该值;如果属性值是对象或数组,则复制的是对象或数组的引用,即新对象中的属性与源对象中的属性指向同一块内存地址。
- 浅拷贝只复制了对象的第一层属性,因此新对象和源对象之间的第一层属性是独立的,但如果源对象的属性值是对象或数组,则浅拷贝后的新对象中的该属性值仍然和源对象中的对应属性值共享内存空间。
深拷贝:
- 深拷贝是指将源对象的所有属性值都复制到新对象中,并且递归复制所有嵌套的对象和数组,使得新对象与源对象完全独立,不共享内存地址。
- 深拷贝后的新对象与源对象之间的所有属性值都是独立的,修改其中一个对象的属性值不会影响另一个对象的属性值。
深拷贝方法:
1.使用递归的方法遍历
function deepCopy(obj) {
if (obj === null || typeof obj !== 'object') {
return obj; // 如果是基本类型或 null,则直接返回
}
let newObj = Array.isArray(obj) ? [] : {}; // 创建新对象,根据源对象的类型确定是对象还是数组
for (let key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
newObj[key] = deepCopy(obj[key]); // 递归复制源对象的每个属性值到新对象中
}
}
return newObj; // 返回新对象
}
deepCopy 函数可以对包含对象或数组的对象进行深拷贝,保证新对象与源对象完全独立。需要注意的是,这种实现方式可能存在循环引用的情况,即源对象中的某个属性值指向了源对象本身或其祖先对象,这种情况下可能导致递归无限循环,因此在实际使用时可能需要进行循环引用的处理。
2.使用JSON.parse(JSON.stringify(obj))
const obj = { foo: 'bar', nested: { baz: 'qux' } };
const newObj = JSON.parse(JSON.stringify(obj));
console.log(newObj); // 输出 { foo: 'bar', nested: { baz: 'qux' } }
使用 JSON.parse(JSON.stringify(obj)) 也可以实现简单的深拷贝。这种方法的原理是将对象先转换为 JSON 字符串,然后再将 JSON 字符串解析为新的对象,这样就会得到源对象的一个副本,达到了深拷贝的效果。
优点:
实现简单
缺点:
1.无法处理函数、正则表达式等特殊类型:JSON.stringify() 方法会忽略对象的函数、正则表达式等特殊类型,导致在深拷贝后丢失这些类型的信息。
2.无法处理循环引用:当对象中存在循环引用时,JSON.stringify() 会抛出错误,因此无法实现对含有循环引用的对象的深拷贝。
无法处理循环引用例子:
//当对象存在循环引用时,使用 JSON.parse(JSON.stringify(obj)) 方法会抛出错误。
const obj = { foo: 'bar' };
obj.circularRef = obj; // 将对象自身赋值给 circularRef 属性,形成循环引用
// 尝试深拷贝包含循环引用的对象
try {
const newObj = JSON.parse(JSON.stringify(obj));
console.log(newObj); // 这行代码不会被执行
} catch (error) {
console.error(error.message); // 输出 "Converting circular structure to JSON"
}
obj 对象中的 circularRef 属性被赋值为对象自身,形成了循环引用。当尝试使用 JSON.parse(JSON.stringify(obj)) 深拷贝 obj 对象时,JSON 序列化过程中会检测到循环引用,导致抛出错误 “Converting circular structure to JSON”。
总结:
对于包含循环引用的对象,使用 JSON.parse(JSON.stringify(obj)) 方法无法进行深拷贝。