关于深拷贝

135 阅读4分钟

参考leader:深拷贝有这5个段位,你只是青铜段位?还想涨薪?

对象

var obj = {
    a字符串:   "第一层",
    b数值:     1,
    c无穷大:   Infinity,
    dNaN:     NaN,
    e正则:     new RegExp(/ab+c/, 'i'),
    fNull:    null,
    g空对象:   {},
    h:        true,
    iUndefined:        undefined,
    j:        [1,'a',{a:1},obj],
    k2:{
        a:   '第二层',
        b:   'bbb',
        c3:{
            a:'第三层',
            b:1,
            c4:{
                a:'第四层',
                b5:{
                    a:'第五层'
                }
            }
        },
    }
}

// 一个简单的环引用
var huan = {}
huan.a = huan

黄金:JSON.parse(JSON.stringify())

代码

function deepClone(target) { 
    return JSON.parse(JSON.stringify(target)) 
}

image.png

不足

参考文章说环引用报错,这里倒是没发现,环引用的字段直接就是一整个消失不见

其他需要注意的就是:

  • 无穷大、NaN会变成null
  • 正则会变成空对象
  • undefined会让字段消失
  • 环引用导致报错

内层对象如何拷贝

再看对象里的对象是深拷贝还是仅仅拷贝的地址:

image.png

OK,可以看到,对象里的对象也是深拷贝

遇到环会怎么样

报错呀

image.png

铂金:遍历对象

铂金初级

代码

function deepClone(target) {
    const temp = {}
    for (const key in target) {
        temp[key] = target[key]
    }
    return temp
}

image.png

不足

  • ok,我们可以看到,环引用会变成undefied
  • 正则、null会变成对象(看样子好像还是空对象)

除此之外似乎就没有了。

内层对象如何拷贝

那,内层的对象,是深拷贝还是浅拷贝呢?

image.png

看到这里就没必要再接着看下去了,这里可以看到关于对象,我们拷贝的仅仅是地址引用

遇到环会怎么样

遇到环之后,它拷贝的也仅仅是第一层的地址引用罢了

image.png

这是为什么呢?因为我们的函数,仅仅识别了第一层对象,第一层对象里仅仅保存对象的引用,那它拷贝的也就是引用了。

怎么解决呢?递归!遇到对象就递归!

铂金plus:递归

代码

function deepClone(target) {
    // 基本数据类型直接返回
    if (typeof target !== 'object') {
        return target
    }

    // 引用数据类型特殊处理
    const temp = {}
    for (const key in target) {
        // 递归
        temp[key] = deepClone(target[key])
    }
    return temp
}

image.png

遇到环之后会怎样

一整个就是报错的大动作啊,因为环引用递归不完的 image.png

内层对象如何拷贝

这次在看内层对象:

image.png

很棒呀!这次都是深拷贝了!

但是……似乎有些不对呀!
我们看看拷贝出来的对象的j属性:

image.png

嗯.......
怎么是个对象呢?我们的j一开始不是个数组吗?怎么回事?

可以看代码里面,我们仅仅进行了是不是对象的判断,而typeof对引用数据类型进行判断是没那么准确的,所以,为了稍微更加精确点🤏 ,我们需要再加上一个数组的判断!

钻石:加入数组拷贝

下面这个代码,跟上面那段比起来,仅有一行进行了改变

function deepClone(target) {
    // 基本数据类型直接返回
    if (typeof target !== 'object') {
        return target
    }

    // 引用数据类型特殊处理
    
    // 判断数组还是对象
    const temp = Array.isArray(target) ? [] : {} // *** 仅对这行进行了修改 ***
    for (const key in target) {
        temp[key] = deepClone(target[key])
    }
    return temp
}

我们可以看到,j属性存放的,已经变成数组了:

image.png

星耀:解决环引用

上面我们一直没有解决环引用。

其实环引用使用递归解决是会爆栈的,我这里不知道是因为书写不规范还是其他什么原因,要不然是undefined要不然就是整个字段消失不见

但不管怎么说,环引用我们一直没有解决。

问题不能放那里不动,问题是需要解决的!

Map

怎么解决呢?Map

什么是Map呢?我们先来看:
Map的每一项都是一组键值对
一个Map就是一组键值对的集合。
如果有其中也不会有重复的键。如下:

image.png

那我们就可以利用Map中不会有重复的键这一特性,来注册对象。

比如这里有一个对象obj,我们存放在map中: image.png
如果我们看到对象,就从这个Map中的key中寻找有没有相同名字的对象,
如果有,那么就是环引用,直接放Map中对应的value,
如果没有,那就是没有见过的对象,新创建一个对象。

代码

function deepClone(target, map = new Map()) {
    // 基本数据类型直接返回
    if (typeof target !== 'object') {
        return target
    }

    // 引用数据类型特殊处理
    // 判断数组还是对象
    const temp = Array.isArray(target) ? [] : {}

/* + */    if (map.get(target)) {
/* + */        // 已存在则直接返回
/* + */        return map.get(target)
/* + */    }
/* + */    // 不存在则第一次设置
/* + */    map.set(target, temp)

    for (const key in target) {
        // 递归,递归的时候,我们将最外层创建的map也传递进去,这样一个对象树,只会有一个最开始创建的map
        temp[key] = deepClone(target[key], map)
    }
    return temp
}

勇者终将战胜恶龙!!!

image.png

内层对象如何拷贝

内层对象也是深拷贝呀!!!

image.png

王者:全数据类型覆盖

当然,这种我暂时也用不到,暂时先不看了,但是参考文章中有这部分内容,可以跳过去看一看~