JS深克隆浅克隆

吐槽君 分类:javascript

深克隆浅克隆

浅度克隆:原始类型为值传递,对象类型为引用传递。浅克隆的对象的引用值是拷贝对象里的引用,这两个对象里面的引用(如对象里的数组或者内嵌对象)指向的地方是一致的。

深度克隆:所有对象的属性和值均全部复制,即所有新对象的修改不会影响原对象。

有一个数组,包括基本类型,对象和正则。现在对数组进行克隆。

 let arr1 = [10, 20, {name: '小宝贝'}, /^\d+$/, function () {}]; 

 

浅克隆

  1. 浅克隆: 只把第一级克隆一份过来,第二级及以后和原始对象公用相同的堆地址。
  2. 比较常见的方法:...(展开运算符)、 slice、
  3. 函数的克隆通过浅克隆就可以实现,并不影响原函数。因为函数克隆会在内存中单独开辟一片空间,互不影响。
        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())
  1. 基于JSON方法,先把原始对象转换为字符串,再把字符串重新定义为对象,此时实现了内容的深度克隆。
  2. 问题:如果对象中的某一项值是正则或者函数,基于JSON.stringify和JSON.parse处理后就不在是正则(变为空对象)或者函数(变为null)了。
  3. 能正确处理的对象只有Number、String、Array等能够被json表示的数据结构,因此函数这种不能被json表示的类型将不能被正确处理。
                let arr2 = JSON.parse(JSON.stringify(arr1));
		arr1 === arr2       //false
		arr1[2] === arr2[2]         //false
 
2. 自己写一个深克隆
  • 思路:
  1. 把浅克隆的框架先写出来,然后再在上面添加条件判断。
  2. 接下来判断属性是否null, 基本类型,function,正则,日期,做相应的处理。
  3. 判断源对象的上的属性是否属于对象上,而不是原型上,因为我们只是要克隆对象上的属性。
  4. 然后用递归的方式,把该属性作为源对象调用deep函数。
  5. 最后一步要记得返回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;
		}
 

回复

我来回复
  • 暂无回复内容