目录

🔦 JavaScript 对象的访问器属性和描述符

对象有两种 对象属性:

  • 数据属性。常用存储于对象的数据的属性。
  • 访问器属性(accessor property)。本质上是用作 获取和设置值的函数,但是从外部看像常规属性。

# getter / setter

对象字面量 中,使用 getset 表示:

let obj = {
  get propName() {
    // ... 当读取 obj.propName 时起作用
  },
  set propName() {
    // ... 设置 obj.propName 的值时起作用
  }
}
1
2
3
4
5
6
7
8

🌰 例子:

let user = {
  name: "John",
  surname: "Smith",

  get fullName() {
    return `${this.name} ${this.surname}`;
  }
};

console.log(user.fullName) // John Smith 
1
2
3
4
5
6
7
8
9
10

从外部看访问属性就像一个普通的属性,这就是 访问属性的设计思想。不应该使用 函数形式 访问。只要正常读取 访问属性,而 getter 在幕后运行。

在上面的例子中,如果不设置 setter ,此时对 fullName 设值(赋值),就会出错。所以就要给 fullName 添加一个 setter :

let user = {
  name: "John",
  surname: "Smith",

  get fullName() {
    return `${this.name} ${this.surname}`;
  },

  set fullName(value) {
    [this.name, this.surname] = value.split(" ");
  }
};

// 此时赋值
user.fullName = "Alice Cooper" 
console.log(user.name)    // Alice
console.log(user.surname) // Cooper
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

至此, fullName 访问属性可以看作是一个 「虚拟属性」,可读且可写。

# 访问器描述符

访问器属性的描述符与数据属性的不同。对于访问器属性,没有 valuewritable ,但是有 getset 函数。

访问器描述符有:

  • get:一个 没有参数函数,在读取属性时工作。
  • set :带有 一个参数函数,当属性被设置时调用。
  • enumerable :与数据属性的相同。
  • configurable:与数据属性的相同。

🌰 例子 / 使用 defineProperty 创建一个 fullName 的访问器。可以使用 getset 传递描述符。

let user = {
  name: "John",
  surname: "Smith"
};

Object.defineProperty(user, 'fullName', {
  get() {
    return `${this.name} ${this.surname}`;
  },

  set(value) {
    [this.name, this.surname] = value.split(" ");
  }
});

console.log(user.fullName) // John Smith
for(let key in user) alert(key); // name, surname
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

注意

一个属性要么是访问器(具有 get/set 方法),要么是数据属性(具有 value ),但不能两者都是。如果试图在同一个描述符中同时提供 getvalue ,就会报错。

gettersetter 可以用于包装 真实 属性值(包装器),可以对它们实现更多的控制。

🌰 例子:

let user = {
  get name() {
    return this._name
  }
  
  set name(value) {
    if(value.length < 4) {
      console.log("")
    }
    this._name = value
  }
}

user.name = "Pete"
console.log(user.name)

user.name = "A" // 太短
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

name 被存储在 _name 属性中,并通过 gettersetter 进行访问。

从技术上讲,外部代码可以使用 user._name 直接访问 name。但是,这儿有一个众所周知的约定,即以下划线 "_" 开头的属性是内部属性,不应该从对象外部进行访问。

# 访问器的兼容性

访问器的一大用途是,它们允许随时通过使用 gettersetter 替换「正常的」数据属性,来控制和调整这些属性的行为。

🌰 例子:

function User(name, age) {
  this.name = name;
  this.age = age;
}

let john = new User("John", 25);
1
2
3
4
5
6

但是,之后可能会选择 存储 birthday 而不是 age ,选择日期类型的 birthday 可以更加精准,但是 age 又想要保留:

function User(name, birthday) {
  this.name = name;
  this.birthday = birthday;

  // 年龄是根据当前日期和生日计算得出的
  Object.defineProperty(this, "age", {
    get() {
      let todayYear = new Date().getFullYear();
      return todayYear - this.birthday.getFullYear();
    }
  });
}

let john = new User("John", new Date(1992, 6, 1));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
📢 上次更新: 2022/09/02, 10:18:16