对象浅拷贝,深拷贝

187 阅读3分钟

拷贝对象,需要做到2点:

1、新对象与旧对象具有相同的属性

2、新对象与旧对象具有相同的原型

1、对象浅拷贝 copy方法判断要copy的对象是不是数组,如果是就创建一个空数组,否则就创建对象

var obj={
	name:'唐三',
	age:'22',
	phone:'1372476xxxx',
	girlFreind:{
		name:'小兔子'
	}
}
Object.setPrototypeOf(obj,{address:'天斗帝国'})
function copy(obj){
	var newObj =Array.isArray(obj)?[]:{}
	for (let key in obj) {
		newObj[key]=obj[key]
	}
	return newObj
}
var newObj=copy(obj);

问题1:obj和newObj是共享girlFreind这个对象

问题2:obj原型上的address也被copy到newObj对象里

解决问题2,再for in 使用hasOwnProperty方法,判断每一个属性是不是自身的属性:

function copy(obj) {
	var newObj =Array.isArray(obj)?[]:{}
	for (let key in obj) {
		if (obj.hasOwnProperty(key)) {
			newObj[key] = obj[key]
		}
	}
	return newObj
}

问题1,由于newObj只是拷贝了girlFreind这个对象的引用地址,导致无论谁修改这对象,都会互相同步。解决方案,判断每一个属性的值是不是对象或者数组,如果是就重新调用copy保存返回来的对象,否则就直接赋值。

function copy(obj) {
	var newObj =Array.isArray(obj)?[]:{}
	for (let key in obj) {
		if (obj.hasOwnProperty(key)) {
			if(typeof(obj[key])==='object'||Array.isArray(obj[key])){
				newObj[key]=copy(obj[key])
			}else{
				newObj[key] = obj[key]
			}
		}
	}
	return newObj
}


function deepClone(origin,hashMap=new WeakMap()) {
	if (origin == undefined || typeof origin !== 'object') {
		return origin;
	}
	if (origin instanceof Date) {
		return new Date(origin);
	}
	if (origin instanceof RegExp) {
		return new RegExp(origin);
	}
	console.log(hashMap);
	const hashValue=hashMap.get(origin)
	if(hashValue){
		return hashValue;
	}
	const target = new origin.constructor();
	hashMap.set(origin,target)
	for (let key in origin) {
		if (origin.hasOwnProperty(key)) {
			target[key] = deepClone(origin[key],hashMap);
		}
		
	}
	return target;
}

当前的对象自身属性现在已经是深拷贝了,但是访问newObj.address会的到一个undefined。由于newObj并没有拷贝都obj原型上的属性,所以接下来把obj的原型设置为newObj的原型。 Object.create(Object.getPrototypeOf(obj)):

function copy(obj) {
	var newObj =Object.create(Object.getPrototypeOf(obj))
	for (let key in obj) {
		if (obj.hasOwnProperty(key)) {
			if(typeof(obj[key])==='object'||Array.isArray(obj[key])){
				newObj[key]=copy(obj[key])
			}else{
				newObj[key] = obj[key]
			}
		}
	}
	return newObj
}

又有一个问题,如果修改了obj的原型上的属性,newObj也会改变,因为他们指向同一个对象:

// obj.__proto__.address='星斗森林'
Object.getPrototypeOf(obj).address='星斗森林'
console.log(newObj.address)//星斗森林;

这个时候需要对对象的原型进行深度拷贝: 1、通过Object.getPrototypeOf获取对象的原型赋值给变量proto;

2、如果proto不为null且proto不是数组,就进行原型深度拷贝重新赋值给proto;

3、通过Object.setPrototypeOf设置newObj的原型为newObjPrototype,代码如下:第一种深度克隆

function copy(obj) {
	var newObj = Array.isArray(obj) ? [] : {}
	var proto = Object.getPrototypeOf(obj)
	if (proto != null && !Array.isArray(proto)) {
		proto= copy(proto)
		Object.setPrototypeOf(newObj, proto)
	}
	for (let key in obj) {
		if (obj.hasOwnProperty(key)) {
			if (typeof(obj[key]) === 'object' || Array.isArray(obj[key])) {
				newObj[key] = copy(obj[key])
			} else {
				newObj[key] = obj[key]
			}
		}
	}
	return newObj
}

第二种深度克隆:

function deepClone(origin) {
	var proto = Object.getPrototypeOf(origin)
	if (proto != null && !Array.isArray(proto)) {
		proto=deepClone(proto)
	}
	var properties = Object.getOwnPropertyDescriptors(origin)
	for (let key in properties) {
		if (typeof(properties[key].value) === 'object') {
			properties[key].value = deepClone(properties[key].value)
		}
	}
	var obj = Object.create(proto, properties)
	return obj
}

总结:

1、如果需要深度克隆对象,不考虑方法和继承,选择JSON.parse(JSON.stringify(obj))

2、如果考虑继承选择方法2。方法2保存了对象属性的完整信息,比如属性能不能枚举,第一种只是简单newObj[key] = obj[key]赋值,属性的描述信息都是默认值。