JS中如何实现深拷贝
1.实现深拷贝的方法
- JSON.stringify()、JSON.parse()方法组合
let people = {
name:"ivanadmin",
age:"18",
}
JSON.stringify(people)
let people2 = JSON.parse(JSON.stringify(people))
不妨思考一个问题如果拷贝的对象有环那么这种方法是否可行呢?答案是不成功的
不仅如此,JSON.stringify()方法会将函数、undefined、symbol、正则表达式、Date对象等转化为字符串,而JSON.parse()方法会将字符串转化为对象,因此,如果对象中包含函数、undefined、正则表达式、Date对象等,那么使用JSON.stringify()和JSON.parse()组合的方法是无法实现深拷贝的。
- 递归
接下来手撕一下递归的深拷贝方法:
【常见的方法】
function deepClone(obj) {
if(typeof obj !== 'object' || obj === null){
return obj;
}
//创建一个空对象或空数组来存储克隆后的对象
let result = Array.isArray(obj) ? [] : {};
for(let key in obj){
result[key] = deepClone(obj[key]);
}
return result;
}
let obj = {
a: 1,
b: {
c: 2,
d: [3, 4]
}
};
let clonedObj = deepClone(obj);
console.log(clonedObj); // 输出: { a: 1, b: { c: 2, d: [ 3, 4 ] } }
console.log(obj === clonedObj); // 输出: false,说明是深拷贝
【优化方法】主要考虑到循环引用的问题,使用一个WeakMap来记录已经访问过的对象,如果再次访问到已经访问过的对象,则直接返回该对象,而不是重新创建一个新的对象。
为什么使用WeakMap而不是Map?因为WeakMap中的键是弱引用的,当键不再被引用时,键会被垃圾回收机制回收,而Map中的键是强引用的,即使键不再被引用,也不会被垃圾回收机制回收。
let people = {
name:"ivanadmin",
age:"18",
}
people.selfReference = people;
// 那么如何实现深拷贝一个带环的对象呢?
// 这里采用递归+WeakMap实现
// console.log(people);
function deepClone(obj,map=new WeakMap()){
if(typeof obj !=="object" || obj === null){
return obj;
}
//当这个对象被引用过,直接返回引用避免死循环
if(map.has(obj)){
return map.get(obj);
}
//接下来就是逐一判断obj类型了
let result = Array.isArray(obj)?[]:{};//创建一个空对象或者空数组
//如果走到了这一步那么就说明这个对象没有被引用过
map.set(obj,result);
for(let key in obj){
obj.hasOwnProperty(key) && (result[key] = deepClone(obj[key],map));
}
return result;
}
let dog = deepClone(people)
console.log(`拷贝成功${dog}`);
//看下他们是否会指向同一个引用
console.log(dog === people);
//以及他的循环是否被拷贝
console.log(dog.selfReference === people);//false
console.log(dog.selfReference === dog);//true
总结:
递归遍历的深拷贝方法,首先判断对象是否为null或者不是对象类型,如果是则直接返回该对象,否则创建一个空对象或者空数组来存储克隆后的对象。然后遍历对象的属性,如果属性是自身的属性,则递归调用deepClone方法,并将克隆后的属性赋值给新的对象。最后返回新的对象。如果考虑循环引用的问题,可以使用WeakMap来记录已经访问过的对象,如果再次访问到已经访问过的对象,则直接返回该对象,而不是重新创建一个新的对象。避免死循环!