前言

无论是面试中还是实际工作中,遇到深浅拷贝的概率都挺大的。
由于浅拷贝比较简单,本文中就不提了,主要聊一聊深拷贝。
首先列举出深拷贝常见的几种方式:

  • 迭代法
  • 序列化和反序列化
  • Object.create()前方长篇预警...

一、简单对象的深拷贝

对于数据类型简单的对象,如:

let obj = {
    a: {
        b: 1,
        c: 2
    },
    d: [3, 4]
}
复制代码

方法一(for...in):

我们用简单的for...in...递归就可以实现此对象的深拷贝

function deepClone(obj) {
    // obj 不是对象则不执行
    if (typeof obj !== 'object') return;
    
    let res = Array.isArray(obj) ? [] : {};
    
    for (let key in obj) {
        res[key] = typeof obj[key] === 'object' ? deepClone(obj[key]) : obj[key];
    }
}
let obj2 = deepClone(obj);
obj.a.b = 2;
obj.d.push(10);

console.log(obj2.a.b);  // 1
console.log(obj2.d);  // [3, 4]
复制代码

这样 obj2 就是拷贝出的一个新对象,完全不受 obj 中值发生改变的影响。

方法二(Reflect):

function deepClone2(obj) {
    // obj 不是对象则不执行
    if (typeof obj !== 'object') return;
    
    let res = Array.isArray(obj) ? [...obj] : { ...obj }
    Reflect.ownKeys(res).forEach( key => {
        res[key] = typeof obj[key] === 'object' ? deepClone2(obj[key]) : obj[key];
    })
}
let obj2 = deepClone2(obj);
obj.a.b = 2;
obj.d.push(10);

console.log(obj2.a.b);  // 1
console.log(obj2.d);  // [3, 4]
复制代码

这个方法使用了Reflect.ownKeys方法遍历后递归拷贝,我们发现结果和for...in的方法得到的结果一样,那两者的区别是什么呢?我们后面再说。

方法三(JSON.parse):

function deepClone3(obj) {
    return JSON.parse(JSON.stringify(obj))
}
复制代码

使用这种方法的问题在于以下几点

  • 会忽略 undefined
  • 会忽略 symbol
  • 不能序列化函数
  • 不能解决循环引用的对象
  • 会抛弃对象的constructor

也就是这种方式的深拷贝,只能处理那些能用json表示的对象,而且拷贝后,不管这个对象原来的构造函数是什么,都会变成Object类型。

以上就是对于简单对象深拷贝的实现,上述三种方法基本都可以满足需求

二、复杂对象的深拷贝

上面说到的几种方法,对于对象中只存在数组,Object的,可以实现深拷贝,但是如果对象属性中有方法或者其他类型的对象,应该怎么处理呢?

  • 思考来源于阿里的一道面试题
// 实现如下对象的深拷贝
let obj = {
    obj: {
        name: '我是一个对象',
        id: 1
    },
    arr: [0, 1, 2],
    fn: () => {
        console.log('我是一个函数')
    },
    date: new Date(),
    reg: new RegExp('/我是一个正则/ig'),
    err: new Error('我是一个错误')
}
复制代码

对于这样的一个对象,如果运用上面的三种方法,我们发现用前两种方法,得到的结果是这样的

arrobj拷贝成功了,其他的都没有拷贝成功。

用序列化的方法拷贝的话得到的结果是这样的:

我们发现对于数组和 Object以外的其他对象,都会失真,

date只剩一个字符串了。

解决方案

首先对于function类型的值,我们可以先把它转换为字符串,再使用eval方法。
改进后的代码:

function deepClone(obj) {
    // obj 不是对象则不执行
    if (typeof obj !== 'object') return;
    
    let res = Array.isArray(obj) ? [] : {};
    
    for (let key in obj) {
        if (typeof obj[key] === 'object') {
            res[key] = deepClone(obj[key]);
        } else if (typeof obj[key] === 'function') {
            res[key] = eval(obj[key].toString());
        } else {
            res[key] = obj[key];
        }
    }
}
复制代码

未完待续...