面试官:请你手写一个深拷贝

前言:

在之前的文章中提到过关于深拷贝和浅拷贝的概念,给各位详细介绍了一下二者的区别。但是在关于深拷贝的部分,通过JSON.parse和JSON.stringify是无法对null,symbol以及bigint等类型的值进行拷贝甚至报错的。正常情况下,会使用Lodash库去实现深拷贝,但是很显然,在各大卷王的激情贡献下,面试官们已经完全不满足于这个回答了

正文:

递归深拷贝:

在手写前还是要简单说明一下,深拷贝或浅拷贝这一概念仅针对于引用类型的值(比如object,array),而基础类型则无一例外全部都是深拷贝,所以在手写的深拷贝函数的时候,最先需要的就是判断数据类型,是基础类型则直接返回否则再进行深拷贝操作

js 复制代码
function deepCopy(obj) {
    if (obj === null) return null; // 处理null值
    if (!(obj instanceof Object)) return obj // 基本数据类型直接返回
}

随后,判断拷贝的值是数组还是对象并设置返回值

js 复制代码
    let copyObj = Array.isArray(obj) ? [] : {}

接下来就是通过for-in方法去遍历拷贝的值,并且对每个值都要判断是否是自身属性。需要注意一点的是,每个元素都有可能是引用类型,切记不可直接赋值,否则还是浅拷贝。所以应该通过循环调用,也就是递归去不断地往深处寻找,直到找到原始类型的值,就是递归出口

js 复制代码
 for (key in obj) {
        if (obj[key] instanceof Object) {
            copyObj[key] = deepCopy(obj[key])
        } else {
            copyObj[key] = obj[key]
        }
    }

最后,将拷贝后的值返回

js 复制代码
function deepCopy(obj) {
    if (obj === null) return null; // 处理null值
    if (!(obj instanceof Object)) return obj // 基本数据类型直接返回

    let copyObj = Array.isArray(obj) ? [] : {}

    for (key in obj) {
        if (obj[key] instanceof Object) {
            copyObj[key] = deepCopy(obj[key])
        } else {
            copyObj[key] = obj[key]
        }
    }
    return copyObj
}

非递归深拷贝:

当然,如果对象叠加层数非常多,而且还无法对递归进行尾调用处理以至于通过递归深拷贝会造成爆栈(真是多到离谱),那么我们也可以利用weakMap数据类型,通过非递归的方法去进行深拷贝

  1. 首先,还是一样的进行类型判断,然后根据拷贝值的类型去设置返回体
js 复制代码
function deepCopyII(obj, hash = new WeakMap()) {
    if (obj === null) return null; // 处理null值
    if (typeof obj !== 'object') return obj; // 基本数据类型直接返回

    // 检查是否已经拷贝过
    if (hash.has(obj)) return hash.get(obj);

    let cloneObj= Array.isArray(obj) ? [] : {}
}
  1. 随后,设置一个队列,把要拷贝的值和返回值塞进一个数组里面,然后再把数组放进队列
js 复制代码
// 使用队列来存储需要拷贝的对象属性
    let queue = [[obj, cloneObj]];

当队列内部还有值的时候,首先解构出原数据和拷贝数据

js 复制代码
 while (queue.length > 0) {
        let [original, copy] = queue.shift();
  1. 然后通过for-in方法去遍历原数据,这里还是一样,要判断是否自身属性以及原数据是否依旧是引用类型。如果是原始类型,则将原数据的值直接赋值给返回值的对应键或下标。否则就将这个引用类型的值和一个新的对象(数组)塞进新数组并重新放回队列。这样就能保证在不使用递归的前提下能一层层找到原数据的最底层
js 复制代码
for (let key in original) {
            if (original.hasOwnProperty(key)) { // 确保是自身属性
                if (typeof original[key] === 'object' && original[key] !== null) {
                    // 如果属性值也是对象,则递归拷贝
                    if (!hash.has(original[key])) {
                        // 创建新的拷贝
                        let newObj = Array.isArray(original[key]) ? [] : {};
                        hash.set(original[key], newObj);
                        queue.push([original[key], newObj]);
                    }
                    // 将原始对象的属性拷贝到新对象
                    copy[key] = hash.get(original[key]);
                } else {
                    // 如果不是对象,直接赋值
                    copy[key] = original[key];
                }

最后,返回拷贝的值

js 复制代码
function deepCopyII(obj, hash = new WeakMap()) {
    if (obj === null) return null; // 处理null值
    if (typeof obj !== 'object') return obj; // 基本数据类型直接返回

    // 检查是否已经拷贝过
    if (hash.has(obj)) return hash.get(obj);

    let cloneObj;
    if (Array.isArray(obj)) {
        cloneObj = [];
    } else {
        cloneObj = {};
    }

    // 将原始对象和它的拷贝关联起来
    hash.set(obj, cloneObj);

    // 使用队列来存储需要拷贝的对象属性
    let queue = [[obj, cloneObj]];

    while (queue.length > 0) {
        let [original, copy] = queue.shift();

        for (let key in original) {
            if (original.hasOwnProperty(key)) { // 确保是自身属性
                if (typeof original[key] === 'object' && original[key] !== null) {
                    // 如果属性值也是对象,则递归拷贝
                    if (!hash.has(original[key])) {
                        // 创建新的拷贝
                        let newObj = Array.isArray(original[key]) ? [] : {};
                        hash.set(original[key], newObj);
                        queue.push([original[key], newObj]);
                    }
                    // 将原始对象的属性拷贝到新对象
                    copy[key] = hash.get(original[key]);
                } else {
                    // 如果不是对象,直接赋值
                    copy[key] = original[key];
                }
            }
        }
    }

    return cloneObj;
}

总结:

通过这两种方法,我们不仅能够有效地实现深拷贝,还能根据不同的需求和场景选择最适合的实现方式。无论是递归还是非递归深拷贝,都能够确保原始对象和拷贝对象之间是完全独立的,从而避免了修改一个对象时影响另一个对象的问题。最后祝各位看官老爷0 waring(s),0 error(s)。

相关推荐
Captaincc2 小时前
AI用量v0.1.11更新发布 新增 jusage doctor 诊断指令 托盘展示token 和余额 新增 AutoClaw 支持
前端·后端·vibecoding
计算机魔术师4 小时前
德国Wiki被黑后两周,OpenAI终于把模型失控的账本摊开了
前端
kyriewen4 小时前
我让 AI 当面试官面了我一轮:第 3 个追问我就卡住了(附 10 道追问清单)
前端·面试·ai编程
IT_陈寒5 小时前
Python的GIL把我坑惨了,多线程跑得比单线程还慢
前端·人工智能·后端
前端snow5 小时前
ai agent --- 多agent框架之图编排引擎-langgraph
前端
竹林8185 小时前
OmniPic Studio v3.2.1 核心技术架构与全平台发版解析文档
前端·浏览器
JamesZhang800785 小时前
页面内存只涨不跌? 一次泄漏排查, 牵出 WeakMap 的诞生
前端
Z小明5 小时前
第 6 章 组件进阶
前端·vue.js
江华森5 小时前
HTTP请求的完整过程详解:从DNS解析到TCP挥手的微秒级实战分析
前端
南青5 小时前
Vue 3 中后台实战:我踩过的 10 个坑和最佳实践
前端