Class的继承

简介

Class可以通过extends关键字实现继承。

class Point {
	constructor(x, y) {
		this.x = x;
		this.y = y;
	}
}
class ColorPoint extends Point {
	constructor(x, y, color) {
		super();
		this.color = color;
	}
}

子类必须在构造方法中调用super方法,否则新建实例时会报错。ES6的继承机制完全不同,实质是先将父类实例对象的属性和方法,加到this上面(所以必须先调用super方法),然后再用子类的构造函数修改this。
如果子类没有定义构造方法,会默认添加构造方法。

class Point {}
//等同于
class ColorPoint extends Point {
	constructor(...args) {
		super(...args);
	}
}

在子类的构造函数中,只有调用super之后,才能使用this。这是因为子类实例的构建,基于父类实例,只有super方法才能调用父类实例。
从下面的代码可以看出,子类的实例对象同时是父类和子类两个类的实例。

var cp = new ColorPoint(15, 25, "orange");
cp instanceof ColorPoint;		//true
cp instanceof Point;			//true

最后,子类会继承父类的静态方法。

Object.getPrototypeOf()

Objetc.getPrototypeOf可以用来从子类上获取父类。因此,这个方法可以用来判断一个类是否继承了另一个类。

console.log(Object.getPrototypeOf(ColorPoint) == Point);    //true

super关键字

super这个关键字,既可以当作函数使用,也可以当作对象使用。在这两种情况下,它的用法完全不同。
第一种情况,super作为函数调用时,只能用在构造函数中,代表父类的构造函数。

class A {}
class B extends A {
    constructor() {
        super();
    }
}

super虽然代表父类A的构造函数,但返回的是子类B的实例。即super内部的this指的是B的实例,因此super()在这里相当于A.prototype.constructor.call(this);
在下面的代码中new.target指向的是正在执行的函数。new B()输出的是B,说明super内部的this指向的是B。

class A {
	constructor() {
		console.log(new.target.name);
	}
}
class B extends A {
	constructor() {
		super();
	}
}
new A();		//A
new B();			//B

作为函数,super只能用在构造函数中,否则就会报错。

class A {}
class B extends A {
	m() {
		super();
	}
}

第二种情况,super作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。
下面的代码中,子类B当中的super.p(),就是将super当作一个对象使用。这时,super在普通方法之中,指向A.prototype,所以super.p()就相当于A.prototype.p()。因为prop是父类的实例属性,所以就访问不到。

class A {
	constructor() {
		this.prop = 1;
	}
	m() {
		return 2;
	}
}
class B extends A {
	constructor() {
		super();
		console.log(super.m());		// 2
		console.log(super.prop);		//undefined
	}
}
var b = new B();

在子类普通方法中通过super调用父类方法时,方法内部的this指向当前的子类实例。

class A {
	constructor() {
		this.x = 1;
	}
	print() {
		console.log(this.x);
	}
}
class B extends A {
	constructor() {
		super();
		this.x = 2;
	}
	m() {
		super.print();
	}
}
var b = new B();
b.m();		// 2

上面代码中,super.print()虽然调用的是A.prototype.print(),但是A.prototype.print()内部的this指向子类B的实例,导致输出的是2,而不是1。也就是说,实际上执行的是super.print.call(this)。
由于this指向子类,如果通过super对某个属性赋值,这是super就是this。

class A {
	constructor() {
		this.x = 1;
	}
}
class B extends A {
	constructor() {
		super();
		this.x = 2;
		super.x = 3;
		console.log(super.x);		//undefined
		console.log(this.x);		//3
	}
}
var b = new B();

上面的代码中,super.x赋值为3,实际上就是this.x赋值为3。读取super.x的时候,读取的是A.prototype.x。
super作为对象时,在静态方法中指向父类,在普通方法中指向父类原型对象。

class A {
	static myMethod(msg) {
		console.log("static", msg);
	}
	myMethod(msg) {
		console.log("instance", msg);
	}
}
class B extends A {
	constructor() {
		super();
	}
	static myMethod(msg) {
		super.myMethod(msg);
	}
	myMthod(msg) {
		super.myMethod(msg);
	}
}
B.myMethod(1);			//static 1
let b = new B();
b.myMethod(2);			//instance 2

在子类静态方法中通过super调用父类的方法时,方法内部的this指向当前的子类,而不是子类的实例。

class A {
	constructor() {
		this.x = 1;
	}
	static print() {
		console.log(this.x);
	}
}
class B extends A {
	constructor() {
		super();
		this.x = 2;
	}
	static m() {
		super.print();
	}
}
B.x = 3;
B.m();			//3