类继承
类继承是一个类扩展另一个类的一种方式。
因此,我们可以在现有功能之上创建新功能。
- extends 关键字
假设我们有 class Animal:
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");
……然后我们想创建另一个 class Rabbit:
因为 rabbits 是 animals,所以 class Rabbit 应该是基于 class Animal 的,可以访问 animal 的方法,以便 rabbits 可以做“一般”动物可以做的事儿。
扩展另一个类的语法是:class Child extends Parent
我们来创建一个继承自 Animal 的 class Rabbit:
class Rabbit extends Animal {
hide() {
alert(`${this.name} hides!`);
}
}
let rabbit = new Rabbit("White Rabbit");
rabbit.run(5); // White Rabbit runs with speed 5.
rabbit.hide(); // White Rabbit hides!
Class Rabbit 的对象可以访问例如 rabbit.hide() 等 Rabbit 的方法,还可以访问例如 rabbit.run() 等 Animal 的方法。
在内部,关键字 extends 使用了很好的旧的原型机制进行工作。它将 Rabbit.prototype.[[Prototype]] 设置为 Animal.prototype。所以,如果在 Rabbit.prototype 中找不到一个方法,JavaScript 就会从 Animal.prototype 中获取该方法。
例如,要查找 rabbit.run 方法,JavaScript 引擎会进行如下检查
- 查找对象 rabbit(没有 run)。
- 查找它的原型,即 Rabbit.prototype(有 hide,但没有 run)。
- 查找它的原型,即(由于 extends)Animal.prototype,在这儿找到了 run 方法。
这也是为什么日期可以访问通用对象的方法。
在 extends
后允许任意表达式
类语法不仅允许指定一个类,在 extends
后可以指定任意表达式。
例如,一个生成父类的函数调用:
function f(phrase) {
return class {
sayHi() { alert(phrase); }
};
}
class User extends f("Hello") {}
new User().sayHi(); // Hello
这里 class User
继承自 f("Hello")
的结果。
这对于高级编程模式,例如当我们根据许多条件使用函数生成类,并继承它们时来说可能很有用。
重写方法
现在,让我们继续前行并尝试重写一个方法。默认情况下,所有未在 class Rabbit 中指定的方法均从 class Animal 中直接获取。
但是如果我们在 Rabbit 中指定了我们自己的方法,例如 stop(),那么将会使用它:
class Rabbit extends Animal {
stop() {
// ……现在这个将会被用作 rabbit.stop()
// 而不是来自于 class Animal 的 stop()
}
但是通常来说,我们不希望完全替换父类的方法,而是希望在父类方法的基础上进行调整或扩展其功能
。我们在我们的方法中做一些事儿,但是在它之前或之后或在过程中会调用父类方法。
Class 为此提供了"super"
关键字。
执行super.method(...)
来调用一个父类方法。
执行 super(...)
来调用一个父类 constructor(只能在我们的 constructor 中)。
例如,让我们的 rabbit 在停下来的时候自动 hide:
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!`);
}
stop() {
super.stop(); // 调用父类的 stop
this.hide(); // 然后 hide
}
}
let rabbit = new Rabbit("White Rabbit");
rabbit.run(5); // White Rabbit 以速度 5 奔跑
rabbit.stop(); // White Rabbit 停止了。White rabbit hide 了!
现在,Rabbit 在执行过程中调用父类的 super.stop() 方法,所以 Rabbit 也具有了 stop 方法
重写constructor
总结
- 想要扩展一个类:
class Child extends Parent
:- 这意味着
Child.prototype.__proto__
将是Parent.prototype
,所以方法会被继承。
- 这意味着
- 重写一个 constructor:
- 在使用
this
之前,我们必须在Child
的 constructor 中将父 constructor 调用为super()
。
- 在使用
- 重写一个方法:
- 我们可以在一个
Child
方法中使用super.method()
来调用Parent
方法。
- 我们可以在一个
- 内部:
- 方法在内部的
[[HomeObject]]
属性中记住了它们的类/对象。这就是super
如何解析父方法的。 - 因此,将一个带有
super
的方法从一个对象复制到另一个对象是不安全的。
- 方法在内部的
补充:
- 箭头函数没有自己的
this
或super
,所以它们能融入到就近的上下文中,像透明似的。