JavaScript

如何手写一个深拷贝代码

关于深拷贝代码的层层进化

Wenyuan
2021-03-12
4 min

# 用递归的方式手写实现深拷贝

我们先来看网上最常见的深拷贝实现代码

function deepClone(obj) {
    let newObj = Array.isArray(obj) ? [] : {};
    for (let key in obj) {
        let item = obj[key];
        if (item instanceof Object) {
            newObj[key] = deepClone(item);
        } else {
            newObj[key] = item;
        }
    }
    return newObj;
}
1
2
3
4
5
6
7
8
9
10
11
12

测试用例

function Obj() {
    this.num = 1,
        this.str = 'abc',
        this.strObj = new String('def'),
        this.reg = /abc/,
        this.date = new Date(),
        this.arr = [1, 2, 3],
        this.child = {
            id: '1-1',
            child: {
                id: '1-1-1'
            }
        }
    return this;
}
Obj.prototype.p = '原型上的属性p';
let obj = new Obj();
let newObj = deepClone(obj);
// 输出原对象
console.log(obj);
console.log(obj.__proto__.p);
// 输出克隆对象
console.log(newObj);
console.log(newObj.__proto__.p);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

deepClone1 可以发现它的特点有,
对于正则表达式、日期类型无法拷贝,
原始值包装类型,如new String('abc')等不能正常拷贝,
非私有变量,obj原型上的属性不会继承,而是克隆在了自身属性上。

针对以上情况,有如下改进版,
其中,高亮第4行、第7行解决了非私有变量的继承问题,
第13~16行对正则表达式和日期进行额外的判断处理。




 


 





 
 
 
 








function deepClone(obj) {
    if (obj === null) return null;
    // 创建一个使用同一个原型的空对象或数组
    let newObj = new obj.constructor;
    for (let key in obj) {
        // 如果是私有属性才拷贝
        if (obj.hasOwnProperty(key)) {
            let item = obj[key];
            // 如果是函数或基本类型,则直接引用或复制,
            // 否则就是对象类型(正则、日期、数组和对象,排除函数)
            if (typeof item !== 'object') {
                newObj[key] = item;
            } else if (item instanceof RegExp) {
                newObj[key] = new RegExp(item);
            } else if (item instanceof Date) {
                newObj[key] = new Date(item);
            } else {
                newObj[key] = deepClone(item);
            }
        }
    }
    return newObj;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

deepClone2

但对于new String('abc')等,还是不能正常拷贝,
最后,我尝试进行了一些修改,
















 



 






function deepClone(obj) {
    if (obj === null) return null;
    // 创建一个使用同一个原型的空对象或数组
    let newObj = new obj.constructor;
    for (let key in obj) {
        // 如果是私有属性才拷贝
        if (obj.hasOwnProperty(key)) {
            let item = obj[key];
            // 如果是函数或基本类型,则直接引用或复制,
            // 否则就是对象类型(正则、日期、数组、object和原始值包装类型,排除函数)
            if (typeof item !== 'object') {
                newObj[key] = item;
            } else if (item instanceof Array) {
                newObj[key] = deepClone(item);
                // 只有是对象类型中的Object才满足if条件
            } else if (Object.prototype.toString.call(item) === '[object Object]') {
                newObj[key] = deepClone(item);
                // 以下是正则、日期和各类包装类型,用其构造函数实例化一个原来值的实例,这样可以不用分别判断
            } else {
                newObj[key] = new item.constructor(item);
            }
        }
    }
    return newObj;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

deepClone3

以上三个版本,均没有实现对函数的深拷贝,只是引用,貌似也没有这个需求吧。。。
当然,以上的几种深拷贝方法没有绝对的对错,一切要看项目需要考虑到什么程度啦!