JavaScript深拷贝的一些踩坑记录
在 JavaScript 中,深拷贝是比较常见的操作,特别是在处理复杂的数据结构时。但是,JavaScript 中的深拷贝有很多坑点,如果处理不当,就会发生莫名其妙的错误。本文就来总结一下 JavaScript 深拷贝时常见的问题和解决方案。
为什么要进行深拷贝
在 JavaScript 中,对象是通过引用来传递的。这个引用实际上就是指向对象存储位置的指针。如果直接把一个对象赋值给另外一个变量,那么这两个变量就会指向同一个对象。这种情况在开发中很常见,比如一个函数返回了一个对象,然后被调用方修改了这个对象,那么这个修改会对其他地方的引用也生效,进而引发意外的结果。
为了避免这种情况,深拷贝(deep copy)就应运而生了。深拷贝可以创建一个新的对象,这个新对象和原来的对象完全独立,修改新对象不会对原有对象造成影响。
浅拷贝和深拷贝
在进行深拷贝之前,我们先了解一下浅拷贝和深拷贝的区别。
浅拷贝(shallow copy)只是拷贝了一层对象的属性,如果这个对象的属性里面还有对象或者数组,浅拷贝操作并不会将这些对象或者数组进行拷贝,而是共享同一份数据。
const obj = {
name: '张三',
age: 18,
hobbies: ['reading', 'coding', 'sports']
};
const obj2 = Object.assign({}, obj);
console.log(obj.hobbies === obj2.hobbies); // true,obj2.hobbies 与 obj.hobbies 数组指向同一块内存区域
深拷贝(deep copy)则是递归的拷贝对象的属性和子属性,直到所有属性都是基本类型数据。
const obj = {
name: '张三',
age: 18,
hobbies: ['reading', 'coding', 'sports']
};
// 深拷贝可以通过 JSON.parse(JSON.stringfy()) 实现
const obj2 = JSON.parse(JSON.stringify(obj));
console.log(obj.hobbies === obj2.hobbies); // false,obj2.hobbies 和 obj.hobbies 指向不同的内存区域
值得注意的是,JSON 对象有一些限制,不能序列化函数、正则表达式等特殊对象,而且会忽略 undefined 和 symbol 类型的属性,这些情况下则需要用其他方法来实现深拷贝。
踩坑记录
问题1:循环引用
循环引用指的是对象属性中存在指向对象本身的引用。如果在深拷贝时不做处理,就会出现循环引用导致死循环的问题。
const obj = {
name: '张三'
};
obj.me = obj;
// 如果使用 JSON 方式进行深拷贝,会报错:TypeError: Converting circular structure to JSON
const obj2 = JSON.parse(JSON.stringify(obj));
// 如果使用递归方式实现深拷贝,也会陷入死循环
function deepClone(obj) {
const newObj = {};
for (let key in obj) {
if (typeof obj[key] === 'object') {
newObj[key] = deepClone(obj[key]);
} else {
newObj[key] = obj[key];
}
}
return newObj;
}
const obj3 = deepClone(obj); // 报错:Uncaught RangeError: Maximum call stack size exceeded
解决方案:
需要在递归过程中记录已经被处理过的对象,如果下一次递归访问到了已经被处理过的对象,就直接使用之前处理过的结果。
function deepClone(obj, map = new WeakMap()) {
if (map.has(obj)) {
return map.get(obj);
}
const newObj = {};
map.set(obj, newObj); // 记录已经处理的对象
for (let key in obj) {
if (typeof obj[key] === 'object') {
newObj[key] = deepClone(obj[key], map);
} else {
newObj[key] = obj[key];
}
}
return newObj;
}
const obj4 = deepClone(obj); // 正常返回,obj4.me === obj4
问题2:非标准 JSON 对象成员
JSON 对象有一些限制,比如不能序列化函数、正则表达式等类型数据。那么,如果对象中存在这些类型的属性,使用 JSON 方法进行深拷贝时就会出现问题。
const obj = {
name: '李四',
sayHello() {
console.log('Hello!');
},
reg: /test/g
};
const obj2 = JSON.parse(JSON.stringify(obj)); // 报错:TypeError: Converting circular structure to JSON
解决方案:
针对这种情况,可以写一个自定义的序列化函数,在遍历对象属性时,对于函数、正则表达式等非标准对象成员进行特殊处理。
function deepClone(obj, map = new WeakMap()) {
if (map.has(obj)) {
return map.get(obj);
}
const newObj = {};
map.set(obj, newObj);
for (let key in obj) {
if (typeof obj[key] === 'object' && obj[key] !== null) {
newObj[key] = deepClone(obj[key], map);
} else if (typeof obj[key] === 'function') {
newObj[key] = `function ${obj[key].name}() { ... }`;
} else if (obj[key] instanceof RegExp) {
newObj[key] = obj[key].toString();
} else {
newObj[key] = obj[key];
}
}
return newObj;
}
const obj3 = deepClone(obj);
console.log(obj3.reg); // /test/g
总结
JavaScript 中的深拷贝操作并不简单,需要特别注意循环引用、非标准对象成员等情况。在实际开发中,可以根据不同的需求选择不同的深拷贝方式,并且需要对深拷贝方式进行有效的测试和错误处理,确保深拷贝操作不会出现莫名其妙的错误。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:JavaScript深拷贝的一些踩坑记录 - Python技术站