深拷贝代码的层层进化

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

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

1
2
3
4
5
6
7
8
9
10
11
12
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
13
14
15
16
17
18
19
20
21
22
23
24
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);

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

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

{4,7,13-16}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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;
}

deepClone2

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

{16,20}
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
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;
}

deepClone3

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

结构赋值

1
2
3
4
// 对于正则、函数、日期不能拷贝
function deepClone(obj) {
return {...obj};
}

通过 JSON 对象实现深拷贝

通过将对象转换为json字符串,再转换为对象会重新开辟空间

1
2
3
4
5
// 对于正则、函数、日期不能拷贝
function deepClone2(obj) {
var newObj = JSON.parse(JSON.stringify(obj));
return newObj;
}

通过jQuery的extend方法实现深拷贝

1
2
var array = [1,2,3,4];
var newArray = $.extend(true,[],array);

Object.assign()拷贝

1
Object.assign(target, ...sources)

lodash函数库实现深拷贝

lodash很热门的函数库,提供了 lodash.cloneDeep()实现深拷贝