手写深浅拷贝

994 阅读4分钟

前言

这也是大厂经常会问到的高频考点了。

之前自己也写过深浅拷贝的文章,因为主要是对其概念的叙述,所以对其基本概念就不进行介绍了,想了解的朋友们可以参考一下这篇文章

👉👉 再来聊聊深浅拷贝~🐟 🐟 - 掘金 (juejin.cn)

面试官:如何实现深拷贝呢?

JSON.parse(JSON.stringify())

当被问到这个问题的时候,我们脑海中可能想的最多的就是

JSON.parse(JSON.stringify());

如果你仅仅这样告诉面试官的话,肯定戳不到面试官的心巴。 因为这个方法还存在着一些较大的缺陷。

1. 当拷贝对象里有函数的时候,结果会丢失

const test = {
 name: 'a',
 date: function hehe() {
     console.log('fff')
 },
};

const copyed = JSON.parse(JSON.stringify(test));
test.name = 'test'

console.log('copyed', copyed); // copyed { name: 'a' }
// 直接造成了date的丢失

2. 当拷贝的对象里有RegExp对象时,会得到空对象

const test = {
 name: 'a',
 date: new RegExp('\\w+'),
};

const copyed = JSON.parse(JSON.stringify(test));
test.name = 'test'

console.log('copyed', copyed); // copyed { name: 'a', date: {} }

3. 如果对象中存在循环引用的情况也无法正确实现深拷贝

从手写浅拷贝开始

我们的深拷贝,其实也就是在浅拷贝的基础上进行递归,所以实现一个深拷贝,可以从浅拷贝开始了解。

1. 要对对象进行拷贝,肯定也要创建一个新的对象来承接原始的对象

function clone(target) {
    let cloneTarget = {};

    return cloneTarget;
};

2. 进行一对一拷贝

function clone(target) {
    let cloneTarget = {};
    for (const key in target) {
        cloneTarget[key] = target[key];
    }
    return cloneTarget;
};

没错,一个浅拷贝的实现就是这样简单!!

基础版

要想把一个浅拷贝变成深拷贝,只需要进行逐层的递归。

递归的时候要记住

1. 如果是原始类型,无需继续拷贝,直接返回

2. 如果是引用类型,再进行递归,创建一个新的对象,遍历需要克隆的对象。

所以在这里还需要判断一下类型是否为引用类型

function clone(target) {
    if (typeof target === 'object') {
       
    } else {
        return ;
    }
};

再遍历递归对象

function clone(target) {
    if (typeof target === 'object') {
        let cloneTarget = {};
        for (const key in target) {
            cloneTarget[key] = clone(target[key]);
        }
        return cloneTarget;
    } else {
        return target;
    }
};

这里又出现了一个问题了,这要是拷贝的对象是个数组呢?

如果还是用这个代码,连中括号([])都不存在的代码,怎么能得到我们想到的数组呢?

😋😋😋

(数组也是对象呀,不可能不考虑它)

所以接下来,还要对是否为数组进行考虑了

判断数组

 function clone(target) {
    if (typeof target === 'object') {
    // 增加下列一行的代码就能兼容数组了
        let cloneTarget = Array.isArray(target) ? [] : {};
        for (const key in target) {
            cloneTarget[key] = clone(target[key]);
        }
        return cloneTarget;
    } else {
        return target;
    }
};

一般来说,面试官面到这一步,就不会为难你了。但事事有例外~😅

有些面试官就是想再搞一下我们心态,“你能手写个循环引用的情况吗”

image.png

啥是循环引用呀

循环引用

循环引用:即对象的属性间接或直接的引用了自身

不理解没关系,看下面这段测试代码

const obj = {
    a: 1,
    b: 2,
    c: {
        child: 'child'
    },
};
obj.obj = obj;
console.log('obj', obj);

node环境下我们可以得到这样的结果

image.png

这里的Circular就是循环引用的意思

想要更清晰的理解,可以把这测试代码在浏览器上运行一下 image.png

可以看到,这里会一直obj => obj的循环嵌套下去。

让我想起来原来的小故事:从前有座山,山里有座庙,庙里有个小和尚在讲故事,讲的是什么呢:从前有座山,山里有座庙...

🤣🤣🤣

那么如何改进之前的代码呢?

解决循环引用问题,我们可以额外开辟一个存储空间,当需要拷贝当前对象时,先去存储空间中找,有没有拷贝过这个对象,如果有的话直接返回,如果没有的话继续拷贝,这样就巧妙化解的循环引用的问题。

这个存储空间,需要可以存储key-value形式的数据,且key可以是一个引用类型,我们可以选择Map这种数据结构:

  • 检查map中有无克隆过的对象
  • 有 - 直接返回
  • 没有 - 将当前对象作为key,克隆对象作为value进行存储
  • 继续克隆
function clone(target, map = new Map()) {
    if (typeof target === 'object') {
        let cloneTarget = Array.isArray(target) ? [] : {};
        if (map.get(target)) {
            return map.get(target);
        }
        map.set(target, cloneTarget);
        for (const key in target) {
            cloneTarget[key] = clone(target[key], map);
        }
        return cloneTarget;
    } else {
        return target;
    }
};

小结

希望能对屏幕前的你有所帮助吧~

❤️❤️❤️

参考文章

如何写出一个惊艳面试官的深拷贝? - 掘金 (juejin.cn)