🌟秒杀!深拷贝(deepCopy)!前端常见手写题!

182 阅读4分钟

🧑‍💻秒杀!前端常见手写题!-HowieCong

一、深拷贝(deepCopy)

  • 深拷贝是创建一个新对象,新对象与原对象在内存中占据不同的空间,且新对象会递归地复制原对象的所有属性,包括嵌套的对象和数组,这样修改新对象不会影响原对象

  • 常见的实现思路有利用 JSON.parse(JSON.stringify()) 方法、递归遍历对象属性、使用第三方库如 Lodash等

二、实现方法

(1)使用JSON.parse(JSON.stringify())

  • 该方法的原理是先将对象转换为 JSON 字符串,再将 JSON 字符串解析为新对象

  • 局限性,不能处理函数、正则、undefinedSymbol 等特殊类型

function deepCopyByJSON(obj){
    
    const str = JSON.stringify(obj);
    
    return JSON.parse(str);
}
// 测试
const originalObj1 = { a: 1, b: { c: 2 } };
const copiedObj1 = deepCopyByJSON(originalObj1);
console.log(copiedObj1);

(2)递归实现

  • 如果属性是基本数据类型,直接复制

  • 如果是引用数据类型,递归调用深拷贝函数继续复制

function deepCopy(obj) {
    // 如果 obj 是 null 或者不是对象类型,直接返回 obj
    if (obj === null || typeof obj!== 'object') {
        return obj;
    }
    // 判断 obj 是数组还是对象,创建相应的新容器
    const newObj = Array.isArray(obj)? [] : {};
    // 遍历 obj 的所有属性
    for (let key in obj) {
        // 检查属性是否为对象自身的属性,而不是原型链上的属性
        if (obj.hasOwnProperty(key)) {
            // 递归调用 deepCopy 函数复制属性值
            newObj[key] = deepCopy(obj[key]);
        }
    }
    return newObj;
}
// 测试
const originalObj2 = { a: 1, b: { c: 2 } };
const copiedObj2 = deepCopy(originalObj2);
console.log(copiedObj2); 
3. 考虑循环引用的递归深拷贝
  • 为了避免循环引用导致的无限递归问题,使用一个WeakMap来记录已经拷贝过的对象
function deepCopyWithCycle(obj,map = new WeakMap()){
    // 如果obj 是null 或者不是对象类型,直接返回obj
    if(obj === null || typeof obj!== 'object'){
        return obj;
    }
    
    // 检查obj是否已经被拷贝过了
    if(map.has(obj)){
        return map.get(obj);
    }
    
    // 判断obj是数组还是对象,创建相应的新容器
    const newObj = Array.isArray(obj)?[]:{};
    
    // 将obj和新对象的映射关系存入map
    map.set(obj,newObj);
    
    // 遍历obj的所有属性
    for(let key in obj){
        if(obj.hasOwnProperty(key)){
            // 递归调用deepCopyWithCycle函数复制属性值
            newObj[key] = deepCopyWithCycle(obj[key],map);
        }
    }
    return newObj;
}

// 测试
const originalObj3 = {};
originalObj3.self = originalObj3;
const copiedObj3 = deepCopyWithCycle(originalObj3);
console.log(copiedObj3); 

三、讨论

(1)JSON.parse(JSON.stringify()) 方法实现深拷贝有哪些局限性

  • 不能处理函数,因为函数不能被序列化为 JSON 字符串,会被忽略

  • 不能处理正则表达式,会将其转换为空对象

  • 不能处理 undefined 和 Symbol 类型的值,undefined 会被忽略,Symbol 类型的值也会被忽略

  • 不能处理循环引用,会抛出错误

(2)递归实现深拷贝时处理循环引用问题

  • 可以使用一个数据结构(如 WeakMap)来记录已经拷贝过的对象

  • 在递归过程中,每次拷贝一个对象前,先检查该对象是否已经在记录中,如果是,则直接返回记录中的拷贝对象,避免无限递归

(3)深拷贝和浅拷贝的区别

  • 浅拷贝只复制对象的一层属性,如果属性是引用类型,复制的是引用地址,修改原对象或拷贝对象的引用类型属性会相互影响

  • 深拷贝会递归地复制对象的所有属性,包括嵌套的对象,创建一个完全独立的对象,修改原对象不会影响拷贝对象

(4)如何选择合适的深拷贝?

  • 如果对象结构简单,不包含函数、正则、undefinedSymbol 等特殊类型,且不存在循环引用,可以使用 JSON.parse(JSON.stringify()) 方法,代码简洁

  • 果对象结构复杂,包含特殊类型或可能存在循环引用,建议使用递归实现的深拷贝方法,并且考虑处理循环引用的情况

  • 如果项目中已经引入了第三方库如 Lodash,可以直接使用其提供的深拷贝函数,方便且稳定

❓其他

1. 疑问与作者HowieCong声明

  • 如有疑问、出错的知识,请及时点击下方链接添加作者HowieCong的其中一种联系方式或发送邮件到下方邮箱告知作者HowieCong

  • 若想让作者更新哪些方面的技术文章或补充更多知识在这篇文章,请及时点击下方链接添加里面其中一种联系方式或发送邮件到下方邮箱告知作者HowieCong

  • 声明:作者HowieCong目前只是一个前端开发小菜鸟,写文章的初衷只是全面提高自身能力和见识;如果对此篇文章喜欢或能帮助到你,麻烦给作者HowieCong点个关注/给这篇文章点个赞/收藏这篇文章/在评论区留下你的想法吧,欢迎大家来交流!

2. 作者社交媒体/邮箱-HowieCong