9.1 class
基本的类语法看起来像这样:
class MyClass {
prop = value; // 属性
constructor(...) { // 构造器
// ...
}
method(...) {} // method
get something(...) {} // getter 方法
set something(...) {} // setter 方法
[Symbol.iterator]() {} // 有计算名称(computed name)的方法(此处为 symbol)
// ...
// =定义的函数和属性都在对象实例上
// 等价于写在constructor里加this.sayHi = ...
sayHi = function() {
alert('Hi')
}
}
技术上来说,MyClass 是一个函数(我们提供作为 constructor 的那个),而 methods、getters 和 settors 都被写入了 MyClass.prototype。
9.2 类继承
使用 extends 继承
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`);
}
}
class Rabbit extends Animal {
hide() {
alert(`${this.name} hides`);
}
}
let rabbit = new Rabbit("White Rabbit");
rabbit.run(5);
rabbit.hide();
rabbit.__proto__ === Rabbit.prototype;
Rabbit.prototype.__proto__ === Animal.prototype;
Animal.prototype.__proto__ === Object.prototype;
重写方法,使用 super 调用父类的方法
class Rabbit extends Animal {
hide() {
alert(`${this.name} hides!`);
}
stop() {
super.stop(); // 调用父类的 stop
this.hide(); // 然后 hide
}
}
重写 constructor
继承类的 constructor 必须调用 super(…),并且 (!) 一定要在使用 this 之前调用。
class Rabbit extends Animal {
constructor(name, earLength) {
super(name);
this.earLength = earLength;
}
// ...
}
箭头函数没有自己的 super
9.3 静态属性和静态方法
静态方法被用于实现属于整个类的功能。它与具体的类实例无关。
语法如下所示:
class MyClass {
static property = ...;
static method() {
...
}
}
从技术上讲,静态声明与直接给类本身赋值相同:
MyClass.property = ...
MyClass.method = ...
静态属性和方法是可被继承的。
对于 class B extends A,
B.__proto__ === A;
B.prototype.__proto__ === A.prototype;
因此,如果一个字段在 B 中没有找到,会继续在 A 中查找。
继承 Object 的问题
class Rabbit extends Object {
constructor(name) {
super(); // 需要在继承时调用父类的 constructor
this.name = name;
}
}
let rabbit = new Rabbit("Rab");
alert(rabbit.hasOwnProperty("name")); // true
“extends” 语法会设置两个原型:
- 在构造函数的 “prototype” 之间设置原型(为了获取实例方法)。
- 在构造函数之间会设置原型(为了获取静态方法)。
9.4 私有的和受保护的属性和方法
面向对象编程最重要的原则之一 —— 将内部接口与外部接口分隔开来。
在面向对象的编程中,属性和方法分为两组:
- 内部接口 —— 可以通过该类的其他方法访问,但不能从外部访问的方法和属性。
- 外部接口 —— 也可以从类的外部访问的方法和属性。
受保护的 waterAmount
class CoffeeMachine {
// 受保护的属性通常以下划线 _ 作为前缀
_waterAmount = 0;
set waterAmount(value) {
if (value < 0) throw new Error("Negative water");
this._waterAmount = value;
}
get waterAmount() {
return this._waterAmount;
}
constructor(power) {
this.power = power;
}
}
let coffeeMachine = new CoffeeMachine(100);
coffeeMachine.waterAmount = -10;
只读的 power
要让一个属性变得只读,只设置 getter,不设置 setter
class CoffeeMachine {
// ...
constructor(power) {
this._power = power;
}
get power() {
return this._power;
}
}
let coffeeMachine = new CoffeeMachine(100);
coffeeMachine.power = 200; // Error
私有字段
私有字段用#开头,是语言级别的实现
class CoffeeMachine {
#waterLimit = 200;
#checkWater(value) {
if (value < 0) throw new Error("Negative water");
if (value > this.#waterLimit) throw new Error("Too much water");
}
}
let coffeeMachine = new CoffeeMachine();
// 不能从类的外部访问类的私有属性和方法
coffeeMachine.#checkWater(); // Error
coffeeMachine.#waterLimit = 1000; // Error
私有属性限制太严重,更多还是用受保护的字段
就面向对象编程(OOP)而言,内部接口与外部接口的划分被称为 封装
9.5 instanceof 操作符
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