对象的深克隆与浅克隆
说在前面
深克隆对于平常代码用到的地方还是比较多的,而更重要的是,最近学长学姐在面试,可以说这个问题是绕不过去的,面试官基本会问,那就来整理一下吧。
可能平时用的时候深克隆并没有那么完整,但是一定要对其中的思想理解的透彻,如果这篇博客有没有说到的地方,希望大家能指出。
那么先说一下,深克隆针对的是Object这样的引用类型
1.浅克隆
在说深克隆之前我们先来了解一下浅客隆
顾名思义,浅客隆就是复制的很浅,并不深入复制,他只会将这个对象的各个属性进行依次复制,并不会进行递归复制,而JavaScript存储对象都是存地址的,这样的复制并不是完全的复制,通过一段代码来了解吧
function shallowClone(obj) {
const _obj = {};
for ( let i in obj) {
_obj[i] = obj[i];
}
return _obj;
}
//被复制的对象
let oldObj = {
a: 2,
b: 'sss',
c: [0,1,3,4],
d: {
name: 'zzy',
score: {
math: 100,
Chinese: 100,
English: 100
}
}
};
//复制后的对象
let newObj = shallowClone(oldObj);
我们声明一个浅客隆函数,并定义一个对象,然后调用浅客隆,现在我们改变新对象的属性
//改变新对象的属性
newObj.a = 3;
newObj.d = 'haha';
console.log(oldObj); // { a: 2, b: 'sss', c: [ 0, 1, 3, 4 ],d: { name: 'zzy', score: { math: 100, Chinese: 100, English: 100 } } }
console.log(newObj); // { a: 3, b: 'sss', c: [ 0, 1, 3, 4 ], d: 'haha' }
可以看出来对象确实复制成功,然后我们再改变一下嵌套对象中的属性
newObj.d.name = 'ppp';
console.log(oldObj.d.name); //ppp
console.log(newObj.d.name); //ppp
发现嵌套的对象并没有复制成功,他依然是按引用传递的,改变其中一个属性,另一个会随之改变。
原因就是我们只是复制了一层,浅客隆的代码完全可以看出来,只是复制了上面的一层,内层嵌套的并没有复制到,所以oldObj.d.name和newObj.d.name指向的还是同一块内存地址。
现在有许多数组或者对象的复制方法都是浅复制,比如Object.assign
或者扩展运算符实现的复制
2.深克隆
深克隆的方法还是挺多的,但是有一些方法是存在问题的。
(1)JSON.parse方法
JSON对象parse方法可以将JSON字符串反序列化成JS对象,stringify方法可以将JS对象序列化成JSON字符串,这两个方法结合起来就能产生一个便捷的深克隆
let newObj = JSON.parse(JSON.stringify(oldObj));
我们还是用之前的例子
然后修改一下
newObj.d.name = 'ppp';
console.log(newObj.d.name); //ppp
console.log(oldObj.d.name); //zzy
确实,他实现了深克隆,但是这样简单的方法就一定会存在一些问题的
- 对于函数和正则这类特殊对象的克隆,返回会为空
- 对稀疏数组无法复制
- 对象的原型并不会复制
咱们来试一下
function set() {
this.fn = function() {
return 'sss';
};
this.arr = new Array(1);
this.reg = new RegExp('a+c', 'i');
}
function parent() {
this.dad = 'zjf';
this.mom = 'my';
}
set.prototype = new parent();
let oldObj = new set();
let newObj = JSON.parse(JSON.stringify(oldObj));
console.log(c); // parent { fn: [Function], arr: [ \<1 empty item> ], reg: /a+c/i }
console.log(x); // { arr: [ null ], reg: {} }
可以看到出问题了,首先对象的原型并没有复制上,函数和正则复制也出现了问题,稀疏数组也有问题。
那么这个方法是不太靠谱的,但是如果平常确实没有这么多的限制,只是一个普通的简单的对象,可以试一试这个方法,毕竟太方便了~如果有很多的限制,那就得分类处理了!
(2)递归方法
之前的问题我们来一个一个解决
首先是函数和正则特殊对象的克隆,这个好办,只需要判断一下类型,而函数和正则直接返回即可。
稀疏数值通过递归就可以返回,数组里的值为undefined
通过Object.getPrototypeOf
和Object.create
即可将原型复制
来看代码
//判断传值类型
function getType(obj) {
let _toString = Object.prototype.toString;
let map = {
'[object Boolean]' : 'boolean',
'[object Number]' : 'number',
'[object String]' : 'string',
'[object Function]' : 'function',
'[object Array]' : 'array',
'[object Date]' : 'date',
'[object RegExp]' : 'regExp',
'[object Undefined]': 'undefined',
'[object Null]' : 'null',
'[object Object]' : 'object'
};
return map[_toString.call(obj)];
}
//深克隆核心函数
function deepClone(data) {
let obj = null;
let type = getType(data);
if(type === 'array') {
obj = [];
for(let i = 0; i < data.length; i++) {
obj.push(deepClone(data[i]));
}
} else if(type === 'object') {
//获取原型,并创建新的对象,新对象原型为之前对象的
let _proto = Object.getPrototypeOf(data);
obj = Object.create(_proto);
for(let key in data) {
//判断属性是否是实例的,如果是复制
if(data.hasOwnProperty(key)) {
obj[key] = deepClone(data[key]);
}
}
} else {
return data;
}
return obj;
}
//构造函数
function set() {
this.fn = function() {
return 'sss';
};
this.arr = new Array(1);
this.reg = new RegExp('a+c', 'i');
}
function parent() {
this.dad = 'zjf';
this.mom = 'my';
}
set.prototype = new parent();
let oldObj = new set();
let newObj = deepClone(oldObj);
console.log(oldObj);
//
parent {
name: 'ddd',
sex: 'man',
num: '04161083',
reg: /a+c/i,
aR: [ <2 empty items> ] }
console.log(newObj);
//
parent {
name: 'ddd',
sex: 'man',
num: '04161083',
reg: /a+c/i,
aR: [ undefined, undefined ] }
这样就是一个相对还不错的深克隆函数了,将大部分的问题都考虑到了
如果平时要用到深克隆的话,推荐lodash,自己实现一下就是为了学习思想~