目录

⌛️ JavaScript 对象的复制与引用

与原始类型相比,对象的根本区别之一是对象是「通过引用」被存储和复制的,与原始类型值相反:字符串,数字,布尔值等 —— 始终是以「整体值」的形式被复制的。

🌰 例子:

let user = {
  name: 'simon'
}
1
2
3

在对象中,赋值了对象的变量存储的不是对象本身,而是该对象「在内存中的地址」 —— 换句话说就是对该对象的「引用」。

上述例子中,对象内容被存储在内存中的某个位置,而变量 user 保存的是对其的「引用」。(可以想象对象变量一张写有对象的地址的纸) 如果要对对象执行操作,例如获取属性,JavaScript 引擎会查看该地址中的内容,并在实际对象上执行操作。

🌰 例子:

let admin = user; // 赋值引用
1

当一个对象变量被复制 —— 引用被复制,而该对象自身并没有被复制。此时两个对象变量,保存的都是对同一个对象的引用。

🌰 例子:

let user = { name: 'simon' };
let admin = user;
admin.name = 'bimon'; // 通过 "admin" 引用来修改
alert(user.name);
1
2
3
4

此时修改对象的内容:可以通过其中任意一个变量访问该对象,并且修改它的内容。(带有两把钥匙的柜子,届可以更改柜子里面的内容)

# 通过引用比较

仅当两个对象为同一对象时,两者才相等。

🌰 比较两个引用自同一个对象的对象变量:

let a = {};
let b = a; // 复制引用

alert( a == b ); // true,都引用同一对象
alert( a === b ); // true
1
2
3
4
5

🌰 比较两个看似「相同」的对象变量:

let a = {};
let b = {}; // 两个独立的对象

alert( a == b ); // false
1
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,能从另外一个看到变更的结果
1
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); 
1
2
3
4
5
6
7

虽然 user 的值是一个常量。但这里的「常量」指的是始终引用同一个对象。而该对象的值可以被修改。

只有试图对该变量的整体进行赋值时,才会报错。

创建常量的对象属性:属性标志和属性描述符 (opens new window)

📢 上次更新: 2022/09/02, 10:18:16