⌛️ JavaScript 对象的复制与引用
与原始类型相比,对象的根本区别之一是对象是「通过引用」被存储和复制的,与原始类型值相反:字符串,数字,布尔值等 —— 始终是以「整体值」的形式被复制的。
🌰 例子:
let user = {
name: 'simon'
}
2
3
在对象中,赋值了对象的变量存储的不是对象本身,而是该对象「在内存中的地址」 —— 换句话说就是对该对象的「引用」。
上述例子中,对象内容被存储在内存中的某个位置,而变量 user
保存的是对其的「引用」。(可以想象对象变量为一张写有对象的地址的纸) 如果要对对象执行操作,例如获取属性,JavaScript 引擎会查看该地址中的内容,并在实际对象上执行操作。
🌰 例子:
let admin = user; // 赋值引用
当一个对象变量被复制 —— 引用被复制,而该对象自身并没有被复制。此时两个对象变量,保存的都是对同一个对象的引用。
🌰 例子:
let user = { name: 'simon' };
let admin = user;
admin.name = 'bimon'; // 通过 "admin" 引用来修改
alert(user.name);
2
3
4
此时修改对象的内容:可以通过其中任意一个变量访问该对象,并且修改它的内容。(带有两把钥匙的柜子,届可以更改柜子里面的内容)
# 通过引用比较
仅当两个对象为同一对象时,两者才相等。
🌰 比较两个引用自同一个对象的对象变量:
let a = {};
let b = a; // 复制引用
alert( a == b ); // true,都引用同一对象
alert( a === b ); // true
2
3
4
5
🌰 比较两个看似「相同」的对象变量:
let a = {};
let b = {}; // 两个独立的对象
alert( a == b ); // false
2
3
4
实际上,对象的比较很少进行。
# 对象的克隆与合并
如果想要复制一个对象,创建一个独立于原来对象的拷贝。通过两种方法:
遍历对象复制:
let user = { name: "John", age: 30 }; let clone = {}; // 新的空对象 // 将 user 中所有的属性拷贝到其中 for (let key in user) { clone[key] = user[key]; } // 现在 clone 是带有相同内容的完全独立的对象 clone.name = "Pete"; // 改变了其中的数据 alert( user.name ); // 原来的对象中的 name 属性依然是 John
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16使用 Object.assign (opens new window) 方法来达成同样的效果,语法为:
Object.assign(dest, [src1, src2, src3...])
,(dest
目标对象,src...
源对象)let user = { name: "John", age: 30 }; let clone = Object.assign({}, user);
1
2
3
4
5
6通过 Object.assign 合并对象的例子:
let user = { name: "John" }; let permissions1 = { canView: true }; let permissions2 = { canEdit: true }; // 将 permissions1 和 permissions2 中的所有属性都拷贝到 user 中 Object.assign(user, permissions1, permissions2); // 现在 user = { name: "John", canView: true, canEdit: true }
1
2
3
4
5
6
7
8
9如果被拷贝的属性已经存在,则覆盖原来的属性:
let user = { name: "John" }; Object.assign(user, { name: "Pete" }); alert(user.name); // 现在 user = { name: "Pete" }
1
2
3
JavaScript 进阶用法中,可以直接使用结构对象(?)传递语法,
clone = { …user }
完成对象的克隆。Rest 参数与 Spread 语法 (javascript.info) (opens new window)
# 对象的深层克隆
前面所述的对象属性,皆为原始类型。但是对象中的属性也可以是对其他对象的引用。上面的简单克隆拷贝是行不通的,因为到了属性为对象时,简单的复制,仍然为对其他对象的引用拷贝。
let user = {
name: "John",
sizes: {
height: 182,
width: 50
}
};
let clone = Object.assign({}, user);
alert( user.sizes === clone.sizes ); // true,同一个对象
// user 和 clone 分享同一个 sizes
user.sizes.width++; // 通过其中一个改变属性值
alert(clone.sizes.width); // 51,能从另外一个看到变更的结果
2
3
4
5
6
7
8
9
10
11
12
13
14
15
要解决这个问题,应该使用一个拷贝循环来检查 user[key]
的每个值,如果它是一个对象,那么也复制它的结构。这就是所谓的「深拷贝」。可以使用 递归 实现,在 lodash (opens new window) 库的 _.cloneDeep(obj) (opens new window)。
注意
由于对象的引用存储特性,使用 const
声明的对象是可以被修改的。
🌰 例子:
const user = {
name: "John"
};
user.name = "Pete"; // (*)
alert(user.name);
2
3
4
5
6
7
虽然
user
的值是一个常量。但这里的「常量」指的是始终引用同一个对象。而该对象的值可以被修改。只有试图对该变量的整体进行赋值时,才会报错。
创建常量的对象属性:属性标志和属性描述符 (opens new window)