对象数据存放在堆内存中,对象变量存放在栈内存中,对象变量通过引用数据的堆地址实现对象访问。
对象之间的赋值,是拷贝了堆内存的地址空间,两个变量指向了同一个对象实体,任何一个对对象的修改都会影响另一个变量。
例如:
let obj1 = {
value: 1
};
let obj2 = obj1;
obj2.value = 2;
console.log(obj1.value); //2
为了实现对象之间互不影响,首先对对象的每一个属性都做一次拷贝。例如:
function shallowClone(item) {
let clone = {};
for (let prop in item) {
clone[prop] = item[prop];
}
return clone;
}
let obj1 = {
value: 1
};
let obj3 = shallowClone(obj1);
console.log(obj3.value); //1
obj3.value = 2;
console.log(obj1.value); //1
但是,shallowClone()中对每一个属性采用赋值拷贝,如果属性是对象类型,也只是得到了引用
let compObj1 = {
value: 1,
param: {
id: 1314,
num: 3
}
}
let compObj2 = shallowClone(compObj1);
console.log(compObj2); //{ value: 1, param: { id: 1314, num: 3 } }
compObj2.param.num = 4;
console.log(compObj1.param.num);//4
原因是:compObj1存在值为对象类型的属性,我们也称这种拷贝为 浅拷贝。也就是说,浅拷贝只拷贝了对象的最外层属性。如果我们得到的拷贝对象在每一层属性上都不共享,就称为 深拷贝,深拷贝是一种彻底的克隆,源对象与克隆对象不会相互影响。
深拷贝--采取对对象属性进行递归拷贝的方法
function deepClone(item) {
let clone = {};
for (let prop in item) {
clone[prop] = (item[prop] instanceof Object ?
deepClone(item[prop]) :
item[prop]);
}
return clone;
}
尽管上面看似实现了对象的拷贝,但忽略了很多细节。问题远没有那么简单。
1、拷贝对象与原对象的一致性。
上面的代码代码使用for...in循环遍历属性,但是for...in列出了对象自身及其原型链上的可枚举属性,也就是说不可枚举的属性没有被拷贝,原型也没有得到拷贝。
2、拷贝方法的通用性。
在上面的例子中,只考虑了对象的情况,但实际中,不可避免有人把基本类型 作为item传入,这时会得到一个默认对象,显然不合理。另外,我们应该注意到,数组也存在深浅拷贝的问题,当传入数组时,我们希望得到一个拷贝的数组。
3、拷贝方法的鲁棒性。它是否适用于所有的对象的拷贝?显然,上面的例子并不满足,当对象出现引用循环时,deepClone()将无限递归。
深浅拷贝的实现
1、浅拷贝的实现
1.1 Object.assign()
Object.assign(target, ...sources)实现将一组源对象中的自身可枚举属性(包括Symbol属性)复制到目标对象上。target:目标对象 sources:源对象
let copyObj = Object.assign({}, sourceObj);
缺点:
1、只拷贝了自身的可枚举属性,没有拷贝正确的原型和不可枚举属性。
2、IE不兼容
手动实现浅拷贝
function shallowClone(target) {
//排除非对象和非数组变量
if (!(target instanceof Object)) return target;
let clone = (Array.isArray(target) ? [] : Object.create(target.__proto__))
let keys = Reflect.ownKeys(target); //保证能获取到所有自身属性
for (let k of keys) {
clone[k] = target[k];
}
return clone;
}
此方法支持数组的浅拷贝
2、深拷贝
2.1 借助JSON转化实现深拷贝
let copyObj = JSON.parse(JSON.stringify(target));
存在的问题
1、对某些数据不支持:如Date类型会被转为字符串类型,Undefined和RegExp类型丢失等问题。 2、无法拷贝存在循环引用的对象。 3、拷贝自身可枚举字符串属性,原型链丢失。 4、属性特性丢失。 5、性能较差。
2.2 手动实现深拷贝
function deepClone(target) {
if (!(target instanceof Object) || 'isClone' in obj)
return target;
let clone = null;
if (target instanceof Date)
clone = new obj.constructor();
else if(Array.isArray(target))
clone = [];
else
clone = obj.constructor();
let keys = Reflect.ownKeys(target);
for (let key of keys) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
target['isClone'] = null;
clone[key] = deepClone(target[key]);
delete obj['isClone'];
}
}
return clone;
}
上面的isClone属性用于防止循环引用时发生无限迭代