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

前言:

在之前的文章中提到过关于深拷贝和浅拷贝的概念,给各位详细介绍了一下二者的区别。但是在关于深拷贝的部分,通过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)。

相关推荐
m0_7482552611 分钟前
easyExcel导出大数据量EXCEL文件,前端实现进度条或者遮罩层
前端·excel
长风清留扬32 分钟前
小程序毕业设计-音乐播放器+源码(可播放)下载即用
javascript·小程序·毕业设计·课程设计·毕设·音乐播放器
web147862107231 小时前
C# .Net Web 路由相关配置
前端·c#·.net
m0_748247801 小时前
Flutter Intl包使用指南:实现国际化和本地化
前端·javascript·flutter
飞的肖1 小时前
前端使用 Element Plus架构vue3.0实现图片拖拉拽,后等比压缩,上传到Spring Boot后端
前端·spring boot·架构
青灯文案11 小时前
前端 HTTP 请求由 Nginx 反向代理和 API 网关到后端服务的流程
前端·nginx·http
m0_748254881 小时前
DataX3.0+DataX-Web部署分布式可视化ETL系统
前端·分布式·etl
ZJ_.1 小时前
WPSJS:让 WPS 办公与 JavaScript 完美联动
开发语言·前端·javascript·vscode·ecmascript·wps
GIS开发特训营1 小时前
Vue零基础教程|从前端框架到GIS开发系列课程(七)响应式系统介绍
前端·vue.js·前端框架·gis开发·webgis·三维gis
Cachel wood2 小时前
python round四舍五入和decimal库精确四舍五入
java·linux·前端·数据库·vue.js·python·前端框架