克隆对象在业务开发中经常遇到,特别是一些底层库的封装过程中。本文介绍浅克隆和深克隆和对应的的实现方案。
什么是浅克隆
下面是浅克隆的一个实现案例:
/**
* 实现方案一
* @param {*} obj
*/
function simpleClone(obj) {
var newObj = {};
for(var key in obj){
console.log('key:', key, obj[key]);
if(!obj.hasOwnProperty(key)){
break;
}
newObj[key] = obj[key];
}
return newObj;
}
/**
* 实现方案二 用es6中的...
*/
function simpleClone2(obj){
return {...obj}
}
var obj = {
a:1,
b:'ssssss',
c: {
x:5,
y:{
g:8
}
}
}
var newObj = simpleClone2(obj)
console.log('obj', obj);
console.log('newObj', newObj);
console.log(obj === newObj); //false
console.log(obj.c === newObj.c); //true 说明没有克隆成功 都用的同一个引用
浅拷贝除了上面的两种实现方案外,使用Object.assign()也是很常用的一种方案。
let obj1 = { person: {name: "kobe", age: 41},sports:'basketball' };
let obj2 = Object.assign({}, obj1);
obj2.person.name = "wade";
obj2.sports = 'football'
console.log(obj1); // { person: { name: 'wade', age: 41 }, sports: 'basketball' }
发现只有对象的最外层克隆了,属性如果是对象类型,克隆后指向的还是同一个引用。
这个时候就需要深克隆登场了
实现一个深克隆
简单方案:
/**
* 简单深度克隆
*/
function deepClone(obj){
return JSON.parse(JSON.stringify(obj))
}
如此对正则、函数、new类型的属性会被忽略掉,如果对象中有这些类型时,该方案是不可行的
/**
*
* 完善的深克隆
*/
function deepClone(obj){
if(typeof obj !== 'object'){
return obj;
}
if(obj === null){
return obj;
}
if(obj instanceof RegExp){
//是正则
return new RegExp(obj);
}
if(obj instanceof Date){
return new Date(obj);
}
if(obj instanceof Function){
return new Function(obj)
}
var newObj = {};
for(var key in obj){
if(obj.hasOwnProperty(key)){
newObj[key] = deepClone(obj[key]);
}
}
return newObj;
}
var obj = {
a:1,
b:'ssssss',
c: {
x:5,
y:{
g:8
}
},
d: /ss$/,
e: new Date(),
f: function(){
console.log(88888);
}
}
var newObj = deepClone(obj)
console.log('obj', obj);
console.log('newObj', newObj);
console.log(obj === newObj); //false
console.log(obj.c === newObj.c); //false
此时可能还有一种场景,如果要进行深拷贝的对象属性存在循环引用呢?对象的循环引用,即对象的属性直接引用了自身的情况。解决这个问题,可以额外开辟一个空间来存储,存储当前对象和引用对象的对应关系,当需要拷贝当前对象时,先去存储空间找,如果找到了就直接返回,如果没有就继续拷贝,并记录下来。
function deepClone(obj, hash = new WeakMap()) {
if (obj === null) return obj; // 如果是null或者undefined我就不进行拷贝操作
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
// 可能是对象或者普通的值 如果是函数的话是不需要深拷贝
if (typeof obj !== "object") return obj;
// 是对象的话就要进行深拷贝
if (hash.get(obj)) return hash.get(obj);
let cloneObj = new obj.constructor();
// 找到的是所属类原型上的constructor,而原型上的 constructor指向的是当前类本身
hash.set(obj, cloneObj);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
// 实现一个递归拷贝
cloneObj[key] = deepClone(obj[key], hash);
}
}
return cloneObj;
}
let obj = { name: 1, address: { x: 100 } };
obj.o = obj; // 对象存在循环引用的情况
let d = deepClone(obj);
obj.address.x = 200;
console.log(d);
参考资料
总结
本文对比了浅拷贝和深拷贝,并介绍了浅拷贝和深拷常见的几种实现方案。如果对你有帮助,可以给作者点赞加关注呀。迎着风的方向。