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 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!
extends
将Rabbit.prototype.[[Prototype]]
设置为 Animal.prototype
在extends后允许任意表达式
function f(phrase) {
return class {
sayHi() {
alert(phrase); }
};
}
class User extends f("Hello") {
}
new User().sayHi(); // Hello
class Rabbit extends Animal {
stop() {
// ……现在这个将会被用作 rabbit.stop()
// 而不是来自于 class Animal 的 stop()
}
}
super
关键字super.method(...)
来调用一个父类方法
。super(...)
来调用一个父类 constructor
(只能在我们的 constructor
中)。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 了!
箭头函数没有super
//箭头函数没有super,如果被访问,它会从外部函数获取
class Rabbit extends Animal {
stop() {
//箭头函数的super和stop的是一样的
setTimeout(() => super.stop(), 1000); // 1 秒后调用父类(Animal)的 stop
}
}
class Rabbit extends Animal {
stop() {
//如果这里是普通的函数,将会抛出错误
setTimeout(function() {
super.stop() }, 1000);
}
}
如果一个类扩展了另一个类并且没有 constructor,那么将生成下面这样的“空” constructor
class Rabbit extends Animal {
// 为没有自己的 constructor 的扩展类生成的
constructor(...args) {
//调用了父类的 constructor,并传递了所有的参数
super(...args);
}
}
继承类的 constructor
必须调用 super(...)
,并且一定要在使用 this 之前
调用。
在 JavaScript 中,继承类(所谓的“派生构造器”,英文为 “derived constructor”)的构造函数与其他函数之间是有区别的。派生构造器具有特殊的内部属性 [[ConstructorKind]]:"derived"
。
这是一个特殊的内部标签。该标签会影响它的 new
行为:
new
执行一个常规函数时,它将创建一个空对象,并将这个空对象赋值给 this
。constructor
执行时,它不会执行此操作。它期望父类的 constructor
来完成这项工作。派生的 constructor
必须调用 super
才能执行其父类(base)的 constructor
,否则 this
指向的那个对象将不会被创建。并且我们会收到一个报错
。class Animal {
constructor(name) {
this.speed = 0;
this.name = name;
}
// ...
}
class Rabbit extends Animal {
constructor(name, earLength) {
super(name);
this.earLength = earLength;
}
// ...
}
// 现在可以了
let rabbit = new Rabbit("White Rabbit", 10);
alert(rabbit.name); // White Rabbit
alert(rabbit.earLength); // 10
class Animal {
name = 'animal';
constructor() {
alert(this.name); // (*)
}
}
class Rabbit extends Animal {
name = 'rabbit';
}
/*
* 为什么这两个会输出相同的结果呢?
* 因为Rabbit没有自己的构造器,去调用了Animal的构造器
* 而父类构造器(Animal)总是会使用它自己字段的值,而不是重写的那一个
**/
new Animal(); // animal
new Rabbit(); // animal
如果这还不清楚,那么让我们用方法来进行比较。
class Animal {
showName() {
// 而不是 this.name = 'animal'
alert('animal');
}
constructor() {
this.showName(); // 而不是 alert(this.name);
}
}
class Rabbit extends Animal {
showName() {
alert('rabbit');
}
}
new Animal(); // animal
new Rabbit(); // rabbit
类字段
初始化的顺序基类
(还未继承任何东西的那种),在构造函数调用前
初始化。派生类
,在 super()
后立刻初始化。在上面的Animal
和Rabbit
的例子当中,Animal
是基类,所以在构造函数调用之前就已经初始化了。Rabbit
是派生类,new Rabbit()
先去调用了 super()
,执行了父类构造器。父类构造器中有this.name
,但是此时Rabbit
还没有自己的类字段,就使用了Animal
的类字段。Rabbit
在super()
调用之后,才会初始化自己的类字段。
当一个对象方法执行时,它会将当前对象作为this
如果调用super.method(),引擎需要从当前对象的原型中获取method(this.__proto__.method
)
但是,这个方法是行不通的。
let animal = {
name: "Animal",
eat() {
alert(`${
this.name} eats.`);
}
};
let rabbit = {
__proto__: animal,
name: "Rabbit",
eat() {
// 这就是 super.eat() 可以大概工作的方式
/*
*.call(this) 在这里非常重要
*因为简单的调用 this.__proto__.eat() 将在原型的上下文中执行 eat,而非当前对象。
**/
this.__proto__.eat.call(this);
}
};
rabbit.eat(); // Rabbit eats.
但是,如果我们在原型链上再添加一个对象,将会发生变化
let animal = {
name: "Animal",
eat() {
alert(`${
this.name} eats.`);
}
};
let rabbit = {
__proto__: animal,
eat() {
//2.因为刚才传递过来的this = longEar
//所以this.__proto__.eat.call(this)变成了longEar.__proto__.eat.call(this)
//又是rabbit.eat,所以rabbit.eat在不停地循环调用自己,因此它无法进一步的提升
this.__proto__.eat.call(this); // (*)
}
};
let longEar = {
__proto__: rabbit,
eat() {
//1.在调用原型(rabbit)中的call方法,提供了调用对象this = longEar
this.__proto__.eat.call(this); // (**)
}
};
longEar.eat(); // Error: Maximum call stack size exceeded
let animal = {
name: "Animal",
eat() {
// animal.eat.[[HomeObject]] == animal
alert(`${
this.name} eats.`);
}
};
let rabbit = {
__proto__: animal,
name: "Rabbit",
eat() {
// rabbit.eat.[[HomeObject]] == rabbit
super.eat();
}
};
let longEar = {
__proto__: rabbit,
name: "Long Ear",
eat() {
// longEar.eat.[[HomeObject]] == longEar
super.eat();
}
};
// 正确执行
longEar.eat(); // Long Ear eats.
[[HomeObject]]
的存在违反了这个原则,因为方法记住了它们的对象
。[[HomeObject]] 不能被更改,所以这个绑定是永久
的。[[HomeObject]] 仅被用于 super
。所以,如果一个方法不使用 super,那么我们仍然可以视它为自由的并且可在对象之间复制。但是用了 super 再这样做可能就会出错。let animal = {
sayHi() {
//3.animal和这个sayHi绑定在一起(它的[[HomeObject]]是animal),只要调用这个sayHi,就一定是animal调用
alert(`I'm an animal`);
}
};
// rabbit 继承自 animal
let rabbit = {
__proto__: animal,
sayHi() {
//2.rabbit和这个sayHi绑定在一起(它的[[HomeObject]]是rabbit),只要调用这个sayHi,就一定是rabbit调用
super.sayHi();
}
};
let plant = {
sayHi() {
alert("I'm a plant");
}
};
// tree 继承自 plant
let tree = {
__proto__: plant,
sayHi: rabbit.sayHi // 1.tree.sayHi 方法是从 rabbit 复制而来
};
tree.sayHi(); // I'm an animal (?!?)
[[HomeObject]]
是为类和普通对象中的方法
定义的。但是对于对象而言,方法必须确切指定为 method()
,而不是 “method: function()”。let animal = {
eat: function() {
// 这里是故意这样写的,而不是 eat() {...
// ...
}
};
let rabbit = {
__proto__: animal,
eat: function() {
//[[HomeObject]]属性是对于方法来说的,而不是属性
super.eat();
}
};
rabbit.eat(); // 错误调用 super(因为这里没有 [[HomeObject]])