9.1 class

基本的类语法看起来像这样:

  1. class MyClass {
  2. prop = value; // 属性
  3. constructor(...) { // 构造器
  4. // ...
  5. }
  6. method(...) {} // method
  7. get something(...) {} // getter 方法
  8. set something(...) {} // setter 方法
  9. [Symbol.iterator]() {} // 有计算名称(computed name)的方法(此处为 symbol)
  10. // ...
  11. // =定义的函数和属性都在对象实例上
  12. // 等价于写在constructor里加this.sayHi = ...
  13. sayHi = function() {
  14. alert('Hi')
  15. }
  16. }

技术上来说,MyClass 是一个函数(我们提供作为 constructor 的那个),而 methods、getters 和 settors 都被写入了 MyClass.prototype。

9.2 类继承

使用 extends 继承

  1. class Animal {
  2. constructor(name) {
  3. this.speed = 0;
  4. this.name = name;
  5. }
  6. run(speed) {
  7. this.speed = speed;
  8. alert(`${this.name} runs with speed ${this.speed}`);
  9. }
  10. stop() {
  11. this.speed = 0;
  12. alert(`${this.name} stands still`);
  13. }
  14. }
  15. class Rabbit extends Animal {
  16. hide() {
  17. alert(`${this.name} hides`);
  18. }
  19. }
  20. let rabbit = new Rabbit("White Rabbit");
  21. rabbit.run(5);
  22. rabbit.hide();
  23. rabbit.__proto__ === Rabbit.prototype;
  24. Rabbit.prototype.__proto__ === Animal.prototype;
  25. Animal.prototype.__proto__ === Object.prototype;

重写方法,使用 super 调用父类的方法

  1. class Rabbit extends Animal {
  2. hide() {
  3. alert(`${this.name} hides!`);
  4. }
  5. stop() {
  6. super.stop(); // 调用父类的 stop
  7. this.hide(); // 然后 hide
  8. }
  9. }

重写 constructor
继承类的 constructor 必须调用 super(…),并且 (!) 一定要在使用 this 之前调用。

  1. class Rabbit extends Animal {
  2. constructor(name, earLength) {
  3. super(name);
  4. this.earLength = earLength;
  5. }
  6. // ...
  7. }

箭头函数没有自己的 super

9.3 静态属性和静态方法

静态方法被用于实现属于整个类的功能。它与具体的类实例无关。
语法如下所示:

  1. class MyClass {
  2. static property = ...;
  3. static method() {
  4. ...
  5. }
  6. }

从技术上讲,静态声明与直接给类本身赋值相同:

  1. MyClass.property = ...
  2. MyClass.method = ...

静态属性和方法是可被继承的。
对于 class B extends A,

  1. B.__proto__ === A;
  2. B.prototype.__proto__ === A.prototype;

因此,如果一个字段在 B 中没有找到,会继续在 A 中查找。

继承 Object 的问题

  1. class Rabbit extends Object {
  2. constructor(name) {
  3. super(); // 需要在继承时调用父类的 constructor
  4. this.name = name;
  5. }
  6. }
  7. let rabbit = new Rabbit("Rab");
  8. alert(rabbit.hasOwnProperty("name")); // true

“extends” 语法会设置两个原型:

  1. 在构造函数的 “prototype” 之间设置原型(为了获取实例方法)。
  2. 在构造函数之间会设置原型(为了获取静态方法)。

9.4 私有的和受保护的属性和方法

面向对象编程最重要的原则之一 —— 将内部接口与外部接口分隔开来。
在面向对象的编程中,属性和方法分为两组:

  • 内部接口 —— 可以通过该类的其他方法访问,但不能从外部访问的方法和属性。
  • 外部接口 —— 也可以从类的外部访问的方法和属性。

受保护的 waterAmount

  1. class CoffeeMachine {
  2. // 受保护的属性通常以下划线 _ 作为前缀
  3. _waterAmount = 0;
  4. set waterAmount(value) {
  5. if (value < 0) throw new Error("Negative water");
  6. this._waterAmount = value;
  7. }
  8. get waterAmount() {
  9. return this._waterAmount;
  10. }
  11. constructor(power) {
  12. this.power = power;
  13. }
  14. }
  15. let coffeeMachine = new CoffeeMachine(100);
  16. coffeeMachine.waterAmount = -10;

只读的 power

要让一个属性变得只读,只设置 getter,不设置 setter

  1. class CoffeeMachine {
  2. // ...
  3. constructor(power) {
  4. this._power = power;
  5. }
  6. get power() {
  7. return this._power;
  8. }
  9. }
  10. let coffeeMachine = new CoffeeMachine(100);
  11. coffeeMachine.power = 200; // Error

私有字段

私有字段用#开头,是语言级别的实现

  1. class CoffeeMachine {
  2. #waterLimit = 200;
  3. #checkWater(value) {
  4. if (value < 0) throw new Error("Negative water");
  5. if (value > this.#waterLimit) throw new Error("Too much water");
  6. }
  7. }
  8. let coffeeMachine = new CoffeeMachine();
  9. // 不能从类的外部访问类的私有属性和方法
  10. coffeeMachine.#checkWater(); // Error
  11. coffeeMachine.#waterLimit = 1000; // Error

私有属性限制太严重,更多还是用受保护的字段
就面向对象编程(OOP)而言,内部接口与外部接口的划分被称为 封装

9.5 instanceof 操作符

  1. obj instanceof Class;

如果 obj 隶属于 Class 类(或 Class 类的衍生类)或者 构造函数,则返回 true。
instanceof 考虑原型链,如果 Class 在 obj 的原型链中,则返回 true

这里还要提到一个方法 objA.isPrototypeOf(objB),如果 objA 处在 objB 的原型链中,则返回 true。所以,可以将 obj instanceof Class 检查改为 Class.prototype.isPrototypeOf(obj)。

instanceof 只关心 prototype,不关心 constructor