Skip to content

Class 基本语法

  1. “class” 语法
JavaScript
	class MyClass {
	  // class 方法
	  constructor() { ... }
	  method1() { ... }
	  method2() { ... }
	  method3() { ... }
	  ...
	}
1. 类的方法之间没有逗号
  1. 什么是 class?
    1. 在 JavaScript 中,类是一种函数。
  2. 不仅仅是语法糖
    1. 类方法不可枚举。
    2. 类总是使用 use strict
    3. 首先,通过 class 创建的函数具有特殊的内部属性标记 [[IsClassConstructor]]: true
  3. 类表达式
    1. 就像函数一样,类可以在另外一个表达式中被定义,被传递,被返回,被赋值等。
    2. 类似于命名函数表达式(Named Function Expressions),类表达式可能也应该有一个名字。
  4. Getters/setters
    1. 就像对象字面量,类可能包括 getters/setters,计算属性(computed properties)等。
  5. 计算属性名称 […]
  6. Class 字段
    1. 使用类字段制作绑定方法
    2. 正如 函数绑定 一章中所讲的,JavaScript 中的函数具有动态的 this

类继承

类继承是一个类扩展另一个类的一种方式。

  1. “extends” 关键字
JavaScript
	class Animal {
	  constructor(name) {
	    this.speed = 0;
	    this.name = name;
	  }
	  run(speed) {
	    this.speed = speed;
	    alert(`${this.name} runs with speed ${this.speed}.`);
	  }
	  stop() {
	    this.speed = 0;
	    alert(`${this.name} stands still.`);
	  }
	}
	
	let animal = new Animal("My animal");
1. 在 `extends` 后允许任意表达式
  1. 重写方法 Class 为此提供了 "super" 关键字。
    • 执行 super.method(...) 来调用一个父类方法。
    • 执行 super(...) 来调用一个父类 constructor(只能在我们的 constructor 中)。
    1. 箭头函数没有 super
  2. 重写 constructor
    1. 对于重写 constructor 来说,则有点棘手。
    2. 到目前为止,Rabbit 还没有自己的 constructor
    3. 继承类的 constructor 必须调用 super(...),并且 (!) 一定要在使用 this 之前调用。 - 当通过 new 执行一个常规函数时,它将创建一个空对象,并将这个空对象赋值给 this。 - 但是当继承的 constructor 执行时,它不会执行此操作。它期望父类的 constructor 来完成这项工作。
    4. 重写类字段: 一个棘手的注意要点
      1. 换句话说,父类构造器总是会使用它自己字段的值,而不是被重写的那一个。
  3. 深入:内部探究和 [[HomeObject]]
  4. [[HomeObject]]
    1. 当一个函数被定义为类或者对象方法时,它的 [[HomeObject]] 属性就成为了该对象。
    2. 然后 super 使用它来解析(resolve)父原型及其方法。
  5. 方法并不是“自由”的
    1. [[HomeObject]] 的存在违反了这个原则,因为方法记住了它们的对象。
    2. [[HomeObject]] 不能被更改,所以这个绑定是永久的。
  6. 方法,不是函数属性
    1. [[HomeObject]] 是为类和普通对象中的方法定义的。但是对于对象而言,方法必须确切指定为 method(),而不是 "method: function()"

静态属性和静态方法

我们还可以为整个类分配一个方法。这样的方法被称为 静态的(static)

JavaScript
	class User {
	  static staticMethod() {
	    alert(this === User);
	  }
	}
	
	User.staticMethod(); // true
1. 静态方法不适用于单个对象
  1. 静态属性
    1. 静态的属性也是可能的,它们看起来就像常规的类属性,但前面加有 static
  2. 继承静态属性和方法
    1. 静态属性和方法是可被继承的。
JavaScript
class Animal {
  static planet = "Earth";

  constructor(name, speed) {
    this.speed = speed;
    this.name = name;
  }

  run(speed = 0) {
    this.speed += speed;
    alert(`${this.name} runs with speed ${this.speed}.`);
  }

  static compare(animalA, animalB) {
    return animalA.speed - animalB.speed;
  }

}

// 继承于 Animal
class Rabbit extends Animal {
  hide() {
    alert(`${this.name} hides!`);
  }
}

let rabbits = [
  new Rabbit("White Rabbit", 10),
  new Rabbit("Black Rabbit", 5)
];

rabbits.sort(Rabbit.compare);

rabbits[0].run(); // Black Rabbit runs with speed 5.

alert(Rabbit.planet); // Earth

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

面向对象编程最重要的原则之一 —— 将内部接口与外部接口分隔开来。

  1. 一个现实生活中的例子
  2. 内部接口和外部接口
      • 内部接口 —— 可以通过该类的其他方法访问,但不能从外部访问的方法和属性。
      • 外部接口 —— 也可以从类的外部访问的方法和属性。
  3. 受保护的 “waterAmount”
    1. 受保护的属性通常以下划线 _ 作为前缀。
  4. 只读的 “power”
    1. 对于 power 属性,让我们将它设为只读。有时候一个属性必须只能被在创建时进行设置,之后不再被修改。
    2. getter/setter 函数
    3. 受保护的字段是可以被继承的
  5. 私有的 “#waterLimit”
    1. 私有字段不能通过 this[name] 访问

扩展内建类

内建的类,例如 ArrayMap 等也都是可以扩展的(extendable)。 内建的方法例如 filtermap 等 —— 返回的正是子类 PowerArray 的新对象。它们内部使用了对象的 constructor 属性来实现这一功能 当 arr.filter() 被调用时,它的内部使用的是 arr.constructor 来创建新的结果数组,而不是使用原生的 Array。这真的很酷,因为我们可以在结果数组上继续使用 PowerArray 的方法。 我们可以给这个类添加一个特殊的静态 getter Symbol.species,它会返回 JavaScript 在内部用来在 map 和 filter 等方法中创建新实体的 constructor

  1. 其他集合的工作方式类似
    1. 其他集合,例如 Map 和 Set 的工作方式类似。它们也使用 Symbol.species
  2. 内建类没有静态方法继承
    1. 内建对象有它们自己的静态方法,例如 Object.keysArray.isArray 等。
    2. 当一个类扩展另一个类时,静态方法和非静态方法都会被继承。这已经在 静态属性和静态方法 中详细地解释过了。

类检查:“instanceof”

instanceof 操作符用于检查一个对象是否属于某个特定的 class。同时,它还考虑了继承。 在许多情况下,可能都需要进行此类检查。例如,它可以被用来构建一个 多态性(polymorphic) 的函数,该函数根据参数的类型对参数进行不同的处理。

  1. instanceof 操作符
    1. obj instanceof Class
    2. 如果 obj 隶属于 Class 类(或 Class 类的衍生类),则返回 true
    3. 如果这儿有静态方法 Symbol.hasInstance,那就直接调用这个方法
    4. 大多数 class 没有 Symbol.hasInstance。在这种情况下,标准的逻辑是:使用 obj instanceOf Class 检查 Class.prototype 是否等于 obj 的原型链中的原型之一。
  2. 福利:使用 Object.prototype.toString 方法来揭示类型
    1. 大家都知道,一个普通对象被转化为字符串时为 [object Object] - 对于 number 类型,结果是 [object Number] - 对于 boolean 类型,结果是 [object Boolean] - 对于 null[object Null] - 对于 undefined[object Undefined] - 对于数组:[object Array] - ……等(可自定义)
  3. Symbol.toStringTag
    1. 可以使用特殊的对象属性 Symbol.toStringTag 自定义对象的 toString 方法的行为。

Mixin 模式

在 JavaScript 中,我们只能继承单个对象。每个对象只能有一个 [[Prototype]]。并且每个类只可以扩展另外一个类。

  1. 一个 Mixin 实例
    1. 在 JavaScript 中构造一个 mixin 最简单的方式就是构造一个拥有实用方法的对象,以便我们可以轻松地将这些实用的方法合并到任何类的原型中。
    2. Mixin 可以在自己内部使用继承。
  2. EventMixin - Mixin 将提供 .trigger(name, [...data]) 方法,以在发生重要的事情时“生成一个事件”。name 参数(arguments)是事件的名称,[...data] 是可选的带有事件数据的其他参数(arguments)。
    • 此外还有 .on(name, handler) 方法,它为具有给定名称的事件添加了 handler 函数作为监听器(listener)。当具有给定 name 的事件触发时将调用该方法,并从 .trigger 调用中获取参数(arguments)。
    • ……还有 .off(name, handler) 方法,它会删除 handler 监听器(listener)。