JavaScript extends 和 super

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 和方法中的不同:
  1. 在 constructor 中 super(…) 意味着 new FooFather(…),所以它并不是指向 FooFather 自身的一个可用的引用,那么引用 super.protytype(FooFather.protytype) 是不工作的。
  2. 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。
发布了45 篇原创文章 · 获赞 3 · 访问量 610

猜你喜欢

转载自blog.csdn.net/qq_40653782/article/details/103973609