简介
Class 可以通过 extends 关键字实现继承,让子类继承父类的属性和方法。extends 的写法比 ES5 的原型链继承,要清晰和方便很多
class Point {}
class ColorPoint extends Point {}
`上面示例中,Point是父类,ColorPoint是子类,它通过 extends 关键字,继承了 Point 类的所有属性和方法。
但是由于没有部署任何代码,所以这两个类完全一样,等于复制了一个 Point 类`
下面,我们在ColorPoint内部加上代码
class Point { /* ... */ }
class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y); // 调用父类的 constructor(x, y)
this.color = color;
}
toString() {
return this.color + ' ' + super.toString(); // 调用父类的 toString()
}
}
constructor() 方法和 toString() 方法内部,都出现了 super 关键字。super 在这里表示父类的构造函数,用来新建一个父类的实例对象
ES6 规定,子类必须在 constructor() 方法中调用 super(),否则就会报错,这是因为子类自己的 this 对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,添加子类自己的实例属性和方法。如果不调用 super() 方法,子类就得不到自己的 this 对象
另一个需要注意的地方是,在子类的构造函数中,只有调用 super() 之后,才可以使用 this 关键字,否则会报错。这是因为子类实例的构建,必须先完成父类的继承,只有 super() 方法才能让子类实例继承父类
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
class ColorPoint extends Point {
constructor(x, y, color) {
this.color = color; // ReferenceError
super(x, y);
this.color = color; // 正确
}
}
如果子类没有定义 constructor() 方法,这个方法会默认添加,并且里面会调用 super()。也就是说,不管有没有显式定义,任何一个子类都有 constructor() 方法
class ColorPoint extends Point {}
// 等同于
class ColorPoint extends Point {
constructor(...args) {
super(...args);
}
}
有了子类的定义,就可以生成子类的实例了
let cp = new ColorPoint(25, 8, 'green');
cp instanceof ColorPoint // true
cp instanceof Point // true
上面示例中,实例对象 cp 同时是 ColorPoint 和 Point 两个类的实例,这与 ES5 的行为完全一致
除了私有属性,父类的所有属性和方法,都会被子类继承,其中包括静态方法
class A {
static hello() {
console.log('hello world');
}
}
class B extends A {}
B.hello() // hello world
子类无法继承父类的私有属性,或者说,私有属性只能在定义它的 class 里面使用
class Foo {
#p = 1;
#m() {
console.log('hello');
}
}
class Bar extends Foo {
constructor() {
super();
console.log(this.#p); // 报错
this.#m(); // 报错
}
}
如果父类定义了私有属性的读写方法,子类就可以通过这些方法,读写私有属性
class Foo {
#p = 1;
getP() {
return this.#p;
}
}
class Bar extends Foo {
constructor() {
super();
console.log(this.getP()); // 1
}
}
super 关键字
super 这个关键字,既可以当作函数使用,也可以当作对象使用。在这两种情况下,它的用法完全不同
第一种情况,super 作为函数调用时,代表父类的构造函数。ES6 要求,子类的构造函数必须执行一次 super 函数
class A {}
class B extends A {
constructor() {
super();
}
}
作为函数时,super() 只能用在子类的构造函数之中,用在其他地方就会报错
class A {}
class B extends A {
m() {
super(); // 报错
}
}
上面代码中,super() 用在 B 类的 m 方法之中,就会造成语法错误
第二种情况,super 作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类
class A {
p() {
return 2;
}
}
class B extends A {
constructor() {
super();
console.log(super.p()); // 2
}
}
let b = new B();
上面代码中,子类 B 当中的 super.p(),就是将 super 当作一个对象使用。
这时,super 在普通方法之中,指向 A.prototype,所以 super.p() 就相当于 A.prototype.p()
这里需要注意,由于 super 指向父类的原型对象,所以定义在父类实例上的方法或属性,是无法通过 super 调用的
class A {
constructor() {
this.p = 2;
}
}
class B extends A {
get m() {
return super.p;
}
}
let b = new B();
b.m // undefined
上面代码中,p 是父类 A 实例的属性,super.p 就引用不到它
如果属性定义在父类的原型对象上,super 就可以取到
class A {}
A.prototype.x = 2;
class B extends A {
constructor() {
super();
console.log(super.x) // 2
}
}
let b = new B();
ES6 规定,在子类普通方法中通过 super 调用父类的方法时,方法内部的 this 指向当前的子类实例
在子类的静态方法中通过 super 调用父类的方法时,方法内部的 this 指向当前的子类,而不是子类的实例
网友评论