【js】对象克隆

1. 写在前面

介绍JavaScript的对象克隆以及两种克隆模式。

2. 对象克隆

对象克隆是对一个对象中的属性进行复制拷贝而产生新的对象。

3. 浅拷贝

先定义一个浅拷贝的函数:

// 浅拷贝函数
function copy(origin){
	let target = {}
	// for..in 遍历的属性包括对象继承的属性
	for(prop in origin){
		// 判断属性是否为对象的自身属性
		if(origin.hasOwnProperty(prop)){
			target[prop] = origin[prop];
		}
	}
	return target;
}

因为for..in会遍历包含对象继承的属性,所以加一个hasOwnProperty方法限制只对对象的自身属性进行拷贝。

然后进行测试:

// 定义一个待拷贝对象
let CloneObj = {
	strAttr: '普通字符串属性',
	arrAttr: ['数组元素01', '数组元素02'],
	objAttr: {
		innerStr: '内置对象字符串属性'
	}
}
// 通过拷贝得到新对象
let newObj = copy(CloneObj);

测试普通属性

// 测试普通属性
console.log('修改之前CloneObj.strAttr:' + CloneObj.strAttr);
newObj.strAttr = '新对象的字符串属性';
console.log('修改之后CloneObj.strAttr:' + CloneObj.strAttr);
console.log('newObj.strAttr:'+ newObj.strAttr);



//打印结果
修改之前CloneObj.strAttr:普通字符串属性
修改之后CloneObj.strAttr:普通字符串属性
newObj.strAttr:新对象的字符串属性

可以看出修改新对象中的普通属性的值不会影响原来对象的属性。

如果对象没有引用属性(数组、对象等)或不会对引用属性进行修改,浅拷贝就能满足需求。

但如果要修改引用属性,将会对原对象的引用属性进行破坏,可以看下面的测试结果便能得知。

测试数组和对象属性

// 测试数组属性
console.log('修改之前CloneObj.arrAttr[0]:' + CloneObj.arrAttr[0]);
newObj.arrAttr[0] = '新数组元素01';
console.log('修改之后CloneObj.arrAttr[0]:' + CloneObj.arrAttr[0]);
console.log('newObj.arrAttr[0]:'+ newObj.arrAttr[0]);

// 测试对象属性
console.log('修改之前CloneObj.objAttr.innerStr:' + CloneObj.objAttr.innerStr);
newObj.objAttr.innerStr = '新内置对象属性';
console.log('修改之后CloneObj.objAttr.innerStr:' + CloneObj.objAttr.innerStr);
console.log('newObj.objAttr.innerStr:'+ newObj.objAttr.innerStr);



// 打印结果
修改之前CloneObj.arrAttr[0]:数组元素01
修改之后CloneObj.arrAttr[0]:新数组元素01
newObj.arrAttr[0]:新数组元素01

修改之前CloneObj.objAttr.innerStr:内置对象字符串属性
修改之后CloneObj.objAttr.innerStr:新内置对象属性
newObj.objAttr.innerStr:新内置对象属性

因为数组或对象属性直接使用=号赋值,只是赋值一个引用指向真正的数据,新对象和源对象使用的真正的数据是同一个。

如果要对非引用属性进行拷贝,就要使用深拷贝。

3. 深拷贝

// 深拷贝函数
function deepCopy(origin){	
	let toStr = Object.prototype.toString;
	let arrStr = '[object Array]';
	// 判断 origin 是数组还是对象
	let target = (toStr.call(origin)==arrStr) ? [] : {};
	// for..in 遍历的属性包括对象继承的属性
	for(prop in origin){
		// 判断属性是否为对象的自身属性
		if(origin.hasOwnProperty(prop)){
			// 判断属性非空以及确定是否为引用属性(使用typeof可以确定数组和对象都是object)
			if(origin[prop] !== null && typeof(origin[prop]) == 'object'){	
				// 若是引用属性,则使用递归赋值
				target[prop] = deepCopy(origin[prop]);
			}else{
				// 普通属性直接赋值( 普通属性也就是原始值类型,Boolean、Number和String等)
				target[prop] = origin[prop];
			}
		}
	}
	return target;
}

两个注意点:

  • 拷贝属性时,要判断是引用属性还是普通属性,普通属性则直接赋值,引用属性则使用递归赋值;
  • 在递归时,需判断是数组属性进行递归赋值还是对象属性进行递归赋值。

4. 参考链接

posted @ 2021-02-19 11:41  叶遮沉阳  阅读(131)  评论(0编辑  收藏  举报