所有的面向对象的语言中,都存在着对象引用、复制等等问题,对于初学者来说可能难以理解。今天我来总结一下JavaScript中对象复制。
首先我们要知道JavaScript中的数据分为基本类型(单类型)和引用类型。除了Object对象,其余都是基本类型例如我们常见的String、Number、boolean、null、undefined。而数组、时间对象以及我们自定义的对象等等,都是继承自Object的,所以说都是引用类型。
在js中对对象进行赋值时,基本类型会被直接复制,例如下:
let a = 1;
let b = a;
b = 2;
console.log(a);
console.log(b);
效果:
可见a,b都是number基本类型,直接赋值就把a复制给了b,改变b并不改变a。
同样可以看这个:
function ch(num) {
num = num + 10;
}
let a = 1;
console.log(a);
ch(a);
console.log(a);
结果:
我们试图通过函数改变a的值,但是没有成功。因为把a作为形参传入后,形参会作为传入实参的一个副本,相当于先做了mun = a的操作,再num自己加10,函数结束,num被销毁,num是a的复制品,num变了也不影响a。
下面建立一个对象,对其进行操作试试看:
//构造角色技能对象
let gzskill = {
skillName: '把你变成布丁',
description: '对敌人造成伤害并回复自身生命值',
injury: 2000
}
//构造一个角色对象
let miyako = {
name: '宫子',
age: '14',
race: 'ghost',
skill: gzskill,
say: () => {
console.log('我~好~恨~啊~');
}
}
let miyakoCopy = miyako;
miyakoCopy.name = '出云宫子';
console.log(miyako);
console.log(miyakoCopy);
这里我建立了个自定义对象miyako,然后新建变量miyakoCopy,改变新建变量,原变量会变吗?结果如下:
可见虽然只改变了新对象,但是原对象也被改了,这是为什么呢?
因为我们自定义的对象属于Object类型,属于引用类型,直接赋值给别的对象时,只是发生了引用,相当于miyako和miyakoCopy两个名字指向了内存中同一空间。如果改变其中一个,另一个也会发生变化。
这在js中属于浅复制,即我们把已有对象赋值给新对象时并没有给新对象在内存中开辟一个新空间,而是发生引用。
同样把对象作为形参传入函数,你会发现它也发生了改变:
//构造角色技能对象
let gzskill = {
skillName: '把你变成布丁',
description: '对敌人造成伤害并回复自身生命值',
injury: 2000
}
//构造一个角色对象
let miyako = {
name: '宫子',
age: '14',
race: 'ghost',
skill: gzskill,
say: () => {
console.log('我~好~恨~啊~');
}
}
function chName(char) {
char.name = '出云宫子';
}
console.log(miyako);
chName(miyako);
console.log(miyako);
结果:
可以理解为,对象作为形参传入后,函数内会先创建副本对象并将传入实参直接赋值给副本,然后再对副本进行操作。而我们传入的是引用类型的数据,因此建立的副本对象也只是对传入实参进行了浅复制。
那么我们想要复制一个对象,即实现深复制,怎么做呢?
可以写一个函数,新建一个对象,并通过遍历、递归方式对原对象的属性进行获取,把每个属性依次给新对象复制。如果属性是基本类型数据例如数值字符串等等,我们就可以直接赋值给新对象属性了,因为基本类型直接赋值就是相当于真正的复制了,如果是引用类型那就进行递归操作:
/**
* 对对象进行深复制
* @param {*} originObject 原对象
* @returns 复制的对象
*/
function copyObject(originObject) {
let destObject = {};
for (let key in originObject) {
let property = originObject[key];
if (property == null) {
destObject[key] = null;
} else if (property == undefined) {
destObject[key] = undefined;
} else if (typeof property == 'object') {
destObject[key] = copyObject(property);
} else {
destObject[key] = property;
}
}
return destObject;
}
然后我们对上面已经构造的对象进行试验:
let miyakoCopy = copyObject(miyako);
miyakoCopy.name = '出云宫子';
miyakoCopy.skill.injury = 3000;
miyakoCopy.say = () => {
console.log('不给布丁就捣蛋!');
}
console.log(miyako);
miyako.say();
console.log(miyakoCopy);
miyakoCopy.say();
结果:
可见通过该方法实现了深复制,无论是对象本身还是对象里面的对象都实现了深复制。
其实在Java、C#等等面向对象的编程语言中,对象的复制、引用都是和上述js中的情况是一样的。