ES6 类提供了在两个函数原型之间建立 [[Prototype]] 链接的语法糖 extends,还提供了自动指向“父构造器”的语法糖 super。本篇文章介绍这两者,请看以下例子,以方便介绍:
class FooFather{
constructor(a,b){
this.x = a;
this.y = b;
}
returnXY(){
return this.x * this.y;
}
}
class FooSon extends FooFather{
constructor(a,b,c){
super(a,b);
this.z = c;
}
returnXYZ(){
return super.returnXY() * this.z;
}
}
var b = new FooSon(1,2,3);
b.x; // 1
b.y; // 2
b.z; // 3
b.returnXYZ(); // 6
一. extends
FooSon extends FooFather 的意思是把 FooSon.prototype 的 [[Prototype]] 链接到 FooFather.prototype。所以子类可以调用父类的方法。因此本例中 returnXYZ 可以调用父类的 returnXY。
事实上不仅是两个函数对象的原型进行了关联,两个函数也进行了关联,从 static 中引用父类方法可以体现,这里不说明。
二. super
在我的《JavaScript class》这篇博客中说到:class 表明创建一个具名的函数,此例中有 FooFather(…),FooSon(…)。 而 returnXY(…),returnXYZ(…) 分别是这两个函数原型上的方法。以下以此来介绍。
如果 super ( super()调用 ) 在 constructor 中,指向的是父级的”构造器” (FooFather(…)),这么说的原因是因为它虽然指向父类,但是它主要是调用父类的 constructor,而不是为了调用父类上的方法。
如果是在方法中,super 指向的是 FooFather 这个“对象”,因而可以访问它的属性(方法)。此例中 super.returnXY() 中 super 具体指向就是 FooFather.prototype。而在 static 方法中,super 指向的是 FooFather 这个函数对象,不是它的 prototype,所以这里统称“对象”。
区分 super 在 constructor 和方法中的不同:
- 在 constructor 中 super(…) 意味着 new FooFather(…),所以它并不是指向 FooFather 自身的一个可用的引用,那么引用 super.protytype(FooFather.protytype) 是不工作的。
- super 在方法中,此时 super 指向 FooFather 这个“对象”。所以 super.constructor 调用也是合法的。但是这个函数只能通过 new 调用,显然它没什么意义(因为不能改变this上下文)。
------------------------------------分割线--------------------------------------------
super 在类里面是静态绑定的
super 并不是像 this 那样动态绑定的,constructor 或者函数方法在声明时建立了 super 引用的话,此时 super 是静态绑定在这个类层次上的。这意味着如果你习惯用“一个类的方法覆盖它的this为另外一个类,从而让另外一个类借用这个类的方法”这种操作的话,如果被借用的类的方法种有 super,结果容易出乎意料。例:
class P1 {
constructor() {
this.id = "a";
}
foo() {
console.log("P1:", this.id);
}
}
class CP1 extends P1{
foo() {
super.foo();
console.log("CP1:", this.id);
}
}
var a = new CP1();
a.foo();
// P1: a
// CP1: a
class P2{
constructor() {
this.id = "b";
}
foo() {
console.log("P2:", this.id);
}
}
class CP2 extends P2{
foo() {
super.foo();
console.log("CP2:", this.id);
}
}
var b = new CP2();
b.foo();
// P2: b
// CP2: b
b.foo().call(a);
// P2: a
// cp2: a
从上面的例子看到,b.foo().call(a) 中 this.id 都被 .call 改变了,打印都为 a,但是 super 中打印的是 P2: a,并不是 P1:a, super 并没有被改变。
所以请注意,super 是静态绑定的(它在构造器或者函数声明时建立 super 引用),它不像this一样动态绑定的。
子类构造器
构造器并不是必须的,如果省略的话,JavaScript 会自动提供一个默认构造器。
默认的子类构造器自动调用父类的构造器并传递所有参数。所以默认的子类构造器可以看作这样:
constructor(...args){
super(...args);
}
注意点:
- 子类构造器必须调用 super(…) 之后才能访问 this,原因你可以把这理解为,初始化实例的时候,创建 this 的是父类的构造器。所以子类构造器调用父类构造器( super(…) )之后,才能使用 this。