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;
}
两个注意点:
- 拷贝属性时,要判断是引用属性还是普通属性,普通属性则直接赋值,引用属性则使用递归赋值;
- 在递归时,需判断是数组属性进行递归赋值还是对象属性进行递归赋值。