「js - classes」class 4

Instance members and static members

Posted by My on August 14, 2023

这是「对象」里两个重大的概念,即「实例成员」和「静态成员」,另外还有一个「访问器成员」,可以控制属性的访问和赋值。

1
2
3
4
5
6
7
8
9
10
11
12
13
class Dog {
  constructor(name, breed, weight) {
    this.name = name;
    this.breed = breed;
    this.weight = weight;
  }
  bark() {
    console.log("Woof!");
  }
  run() {
    console.log("Running...");
  }
}

实例成员

在上述类中,namebreed 等属性是用来描述对象的特征,但是你能说清楚 dog 是什么名字,什么品种吗?不能,这是对象在创建的时候才确定的,像这种跟对象绑定在一起的成员叫做「实例成员」。

要获得名字、品种等信息,不能通过类去获取,而是通过实例对象去获取。其中,类的作用就是给这些实例对象提供模板,定义实例对象的行为和属性,并赋值给实例对象。

在 Js 中,实现面向对象的时候,使用的是原型的方式。通过 new 创建实例对象,运行构造函数,给创建的实例对象属性赋值,方法是挂在原型上的。

这也是原型链的特点,如果某个对象没有某个方法,就通过 __proto__ 到原型上找。

因此,实例成员一部分在对象上,一部分在原型上。

静态成员

静态成员就是直接属于某一类的成员,不属于任何一个对象。比如说,这个 Dog 类别里有多少个 dog,那这个概念如果和实例对象绑在一起,就不太合适。而是应该直接属于这个类,在类里说明 dog 的总数。

在 ES5 中,静态成员就不用放到 prototype 中,而是直接定义在类上。比如 Dog.totalDogs = 0; 这样定义。

在 ES6 中,有更加语义化的方式,即不声明在 constructor 里,而是直接定义在类上 static totalDogs = 0。访问的话,直接通过类名去访问 Dog.totalDogs。静态成员不仅有属性,还有方法。

至于静态成员,我们是很常见的,也用过的。比如, Math 类,里面有一些常用的数学函数,这些函数不依赖于某个实例对象,而是直接属于这个类。

访问器成员

访问器成员是用来控制属性的访问和赋值的。不仅如此,我们可以从概念出发,让某些方法更加语义化。

如类中有一个方法,计算商品总价,是通过 price * quantity 得到的,但是我们希望这个方法更加语义化,比如 totalPrice,这样更加直观。但是如果将 totalPrice 设为一个属性,并在开始时赋值为 price * quantity,那么这在语义上并不明确,意思就是这明明就是这个计算,但却声明成属性。

换句话说,我要这个 totalPrice 是属性,但又有计算的功能,符合方法的特点。这时候,我们就可以用访问器成员来实现。

在 ES5 中,是通过 Object.defineProperty() 来定义访问器成员。

1
2
3
4
5
Object.defineProperty(Dog.prototype, "totalPrice", {
  get: function () {
    return this.price * this.quantity;
  },
});

在 ES6 中,使用更为简单,使用语法糖 「get」来实现。

1
2
3
get totalPrice () {
    return this.price * this.quantity;
  },

我们还可以通过 「访问器」来对属性进行控制,比如说,我们只允许 weight 被修改一次,那么可以这样定义。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Dog {
  constructor(name, breed, weight) {
    this.name = name;
    this.breed = breed;
    this._weight = weight;
  }
  get weight() {
    return this._weight;
  }
  set weight(value) {
    if (!this._weight) {
      this._weight = value;
    } else {
      console.log("Cannot modify weight after it has been set.");
    }
  }
}