JS深克隆浅克隆
分类:javascript
深克隆浅克隆
浅度克隆:原始类型为值传递,对象类型为引用传递。浅克隆的对象的引用值是拷贝对象里的引用,这两个对象里面的引用(如对象里的数组或者内嵌对象)指向的地方是一致的。
深度克隆:所有对象的属性和值均全部复制,即所有新对象的修改不会影响原对象。
有一个数组,包括基本类型,对象和正则。现在对数组进行克隆。
let arr1 = [10, 20, {name: '小宝贝'}, /^\d+$/, function () {}];
浅克隆
- 浅克隆: 只把第一级克隆一份过来,第二级及以后和原始对象公用相同的堆地址。
- 比较常见的方法:...(展开运算符)、 slice、
- 函数的克隆通过浅克隆就可以实现,并不影响原函数。因为函数克隆会在内存中单独开辟一片空间,互不影响。
let arr2 = [...arr1];
console.log(arr1,arr2);
arr1 === arr2 //false
arr1[2] === arr2[2] //true
let arr2 = arr1.slice(0);
深克隆
常见方法
1. JSON.parse(JSON.stringify())
- 基于JSON方法,先把原始对象转换为字符串,再把字符串重新定义为对象,此时实现了内容的深度克隆。
- 问题:如果对象中的某一项值是正则或者函数,基于JSON.stringify和JSON.parse处理后就不在是正则(变为空对象)或者函数(变为null)了。
- 能正确处理的对象只有Number、String、Array等能够被json表示的数据结构,因此函数这种不能被json表示的类型将不能被正确处理。
let arr2 = JSON.parse(JSON.stringify(arr1));
arr1 === arr2 //false
arr1[2] === arr2[2] //false
2. 自己写一个深克隆
- 思路:
- 把浅克隆的框架先写出来,然后再在上面添加条件判断。
- 接下来判断属性是否null, 基本类型,function,正则,日期,做相应的处理。
- 判断源对象的上的属性是否属于对象上,而不是原型上,因为我们只是要克隆对象上的属性。
- 然后用递归的方式,把该属性作为源对象调用deep函数。
- 最后一步要记得返回newObj对象
function deep(obj){
// 如果是null直接返回null
if (obj === null) return null;
// 如果是基本数据值或者函数,也直接返回即可(函数无需克隆处理)
if (typeof obj !== 'object') return obj;
// 如果是正则, 把当前正则复制(克隆)一份
if (_type(obj) === '[object RegExp]') return new RegExp(obj); // 创建一个正则
// 如果是日期格式的数据
if (_type(obj) === '[object Date]') return new Date(obj);
// obj.constructor:找到的是所属类原型上的constructor,而原型上的constructor指向的是当前类本身
// =>保证传递进来什么类型的值,我们最后创建的newObj也是对应类型的
let newObj = new obj.constructor;
for (let key in obj) {
console.log(key)
if (obj.hasOwnProperty(key)) {
//in 不知会判断当前对象中是否包含此属性 还会向原型中查找是否包含此属性
//hasOwnProperty 只会判断当前对象中是否包含此属性
// 如果某一项的值是引用值吗,我们还需要进一步迭代循环,把引用值中的每一项也进一步克隆 =>深度克隆
newObj[key] = _deepClone(obj[key]);
}
}
return newObj;
}
function _type(value) { // 返回数据类型
return Object.prototype.toString.call(value);
}
然后测试一下
let arr1 = [10, 20, {
name: '小红'
}, /^\d+$/, function () {console.log(123)}];
let arr2 = deepe(arr1);
console.log(arr2);
arr2[1]=30
arr2[2].name='test'
arr2[3]='string'
console.log(arr1); // 还是原来的
console.log(arr2);
另外一个例子
let obj1 = {
name: '小明',
ke: ['语文', '数学', '英语'],
color: {
n: 'red',
m: 'blue'
}
};
let obj2 = deep(obj1);
console.log(obj2);
obj2.ke.push('物理')
obj2.color['n']='yellow'
console.log(obj1); // 不会被影响,还是原来的
console.log(obj2);
另外一种写法:先用循环读进来,再进行类型判断,只有object需要二次处理。前一种是先判断,再循环。
function _type(value) {
return Object.prototype.toString.call(value);
}
function _deepClone(obj) { // 必须保证OBJ是数组或者对象等
let newObj = new obj.constructor;
for (let key in obj) {
if (!obj.hasOwnProperty(key)) {;
let item = obj[key],
itemType = _type(item);
if (item !== null && typeof item === "object") { // 不为空,且不是基本类型或函数
if (/(RegExp|Date)/.test(itemType)) { // 只要包含RegExp,Date中的一个
newObj[key] = new item.constructor(item);
continue;
}
// 只有对象才需要深层次克隆处理
newObj[key] = _deepClone(item);
continue;
}
newObj[key] = item;
}
}
return newObj;
}