ECMAScript 6 之Class的继承


让一个构造函数继承另一个构造函数,是常见的需求。ES5 是通过修改原型链实现继承,ES6中Class 可以通过extends关键字实现继承,这比 ES5 的方法要方便很多。

1.ES5的继承

1.1 构造函数的继承

在ES5中实现继承,分两步:

  1. 在子类的构造函数中调用父类的构造函数;
function Sub(value) {
  Super.call(this);
  this.prop = value;
}
  1. 让子类的原型指向父类的原型,子类就会继承父类原型。
Sub.prototype = Object.create(Super.prototype);
Sub.prototype.constructor = Sub;
Sub.prototype.method = '...';

下面是一个具体的例子:

// 父类
function A(x, y) {
	this.x = x;
	this.y = y;
}

// 父类原型添加方法
A.prototype.move = function(x, y) {
    this.x = x;
    this.y = y;
}

// 子类
function B(x, y, z) {
	// 1.调用父类构造函数 
    A.call(this, x, y);
    this.z = z;
}

// 2. 子类原型指向父类原型
B.prototype = Object.create(A.prototype);
B.prototype = B;

// 创建子类实例
let b = new B(0, 0, 0);

b instanceof A; // true
b instanceof B; // true

// 调用父类原型上的方法move
b.move(1, 2); 

上面例子中,instanceof运算符会对子类和父类的构造函数,都返回true

上面写法是子类整体继承父类,有时候只需要继承单个方法,可以使用下面的写法:

Sub.prototype.methodName = function(value) {
	// 调用父类方法
	Super.prototype.methodName.call(this);
	// 实现自己功能
	....
}

1.2 多重继承

JavaScript 不提供多重继承功能,即不允许一个对象同时继承多个对象。但是,可以通过Mixin(混入),实现这个功能。

// 父类A
function A() {
	this.x = "A";
}
// 父类B
function B(){
	this.y = "B"
}

// 子类C
function C() {
	// 1.调用父类构造
	A.call(this);
	B.call(this);
	this. y = "C";
}

// 2. 子类C继承父类A原型
C.prototype = Object.create(A.prototype);
// 3. 子类原型加入父类B原型
Object.assign(C.prototype, B.prototype);

C.prototype.constructor = C;

2. ES6 基于class的继承

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

// 父类
class Super{
	...
}
class Sub extends Super {
	...
}

下面是一个具体例子:

// 父类
class Person {
	constructor(name, age) {
		this.name = name;
		this.age = age;
	}
	toString() {
		return "姓名:" + this.name + " 年龄:" + this.age;
	}
}

// 子类
class Student extends Person {
	constructor(name, age, score) {
		// 调用父类构造
		super(name, age);
		this.score = score;
	}
	toString() {
		return super.toString() + " 成绩:" + this.score;
	}
}

let rycony = new Student("rycony", 24, 89.0);
rycony.toString(); // "姓名:rycony 年龄:24 成绩:89"

上面代码中,在子类构造函数constructor通过super()调用父类构造方法,在子类原型方法toString通过super.toString()调用父类原型上的toString方法。

ES5 的继承实质上是:先创造子类的实例对象this,然后再将父类的方法添加到this上面(Super.call(this))。
ES6 的继承实质上是:先将父类实例对象的属性和方法,加到this上面,然后再用子类的构造函数修改this


如果子类没有定义constructor方法,这个方法会被默认添加。

// 父类A
class A {}

// 子类B继承父类A
class B extends A {}

// 相当于
class B extends A {
	constructor(...args) {
		super(...args);
	}
}

子类的构造函数中,只有调用super之后,才可以使用this关键字,否则会报错。因为**子类实例的构建,基于父类实例**。
// 父类A
class A {
	constructor(x, y) {
		this.x = x;
		this.y = y;
	}
}

// 子类(先使用this)
class B extends A {
	constructor(x, y, z) {
		this.z = z;
		super(x, y);
	}
}
new B(1, 2, 3); // ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor


// 子类(先使用super调用父类构造,再使用this)
class C extends A {
	constructor(x, y, z) {
		super(x, y);
		this.z = z;
	}
}
new C(1, 2, 3); // C {x: 1, y: 2, z: 3}

上面例子中,类B先使用this,会报错。

子类通过extends继承父类,连父类的静态方法也会被继承。

class M {
	static print()  {
		console.info("你好,世界!");
	}
}

class N extends M {
	
}
N.print(); // 你好,世界!

上面例子中,子类N继承了父类M,父类M的静态方法print也会被子类N继承,通过N.print()也能调用父类的静态方法print()

3. Object.getPropertyOf()

Object.getPrototypeOf方法可以用来从子类上获取父类。

class A {}

class B extends A{}

Object.getPropertyOf(B) === A; // true

4. super 关键字

super关键字,既可以当作函数使用,也可以当作对象使用。

4.1 super作为函数调用

super作为函数调用时,代表父类的构造函数。ES6 要求,子类的构造函数必须执行一次super函数

class A {}

class B extends A{
	constructor() {
		this.x = "jidi";
	}
}

new B(); // ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor

上面例子中,子类B继承父类A,但是在子类B构造函数中没有通过super调用父类构造函数,会报错。

super虽然代表了父类的构造函数,但是返回的是子类的实例,即super内部的this指的是子类的实例,因此super()相当于Super.prototype.constructor.call(this)

class A {
	constructor() {
		console.info(new.target.name);
	}
}

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

new A(); // A
new B(); // B

上面例子中,new.target.name指向当前正在执行的函数,在子类B的构造函数通过super()调用父类构造函数,指向的是子类B的构造函数,即super()内部的this指向子类实例。

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

class A {}

class B extends A {
	print() {
		super(); // SyntaxError: 'super' keyword unexpected here
	}
}

4.2 super作为对象调用

super作为对象调用,在普通方法中,指向父类的原型对象;在静态方法中,指向父类

class A {
	constructor(x, y) {
		this.x = x;
		this.y = y;
	}
	m() {
		return "jidi";
	}
	static n() {
		return 23;
	}
}

class B extends A{
	constructor(x, y) {
		super(x, y);
	}
	mm() {
		return super.m(); // super指向父类原型对象
	}
	static nn() {
		return super.n(); // super指向父类
	}
}

let x = new B(1,2);

x.mm(); // "jidi"
B.nn(); // 23

上面例子中,在子类B方法mm中,通过super.m()调用父类A的方法m,此时super指向父类原型对象;在子类静态方法nn中,通过super.n()调用父类静态方法n,此时,super执行父类。(上面例子中,子类的两个方法其实都没有意义,因为子类继承父类,会继承父类原型上的方法和父类静态方法,这里只是作为例子使用)。

在普通方法中,super指向父类的原型对象,定义在父类实例上的方法或属性,是无法通过super调用的。

class A {
	x = "jidi";
	constructor(value) {
		this.y = value;
	}
}

class B extends A {
	constructor(value) {
		super(value);
	}
	print() {
		console.info(super.x);
	}
}

new B(23).print(); // undefined

上面例子中,子类B继承父类A,在方法print中通过super.x想调用父类A的实例属性x,结果为undefined

如果属性定义在父类原型上,super可以获取到属性。

class A { }
A.prototype.x = "jidi";

class B extends A{
	y() {
		return super.x; // 调用父类原型对象属性x
	}
}

new B().y(); // "jidi"

上面例子中,通过super.x就能调用到属性x,因为x定义在父类的原型对象上面。

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

class A {
	constructor() {
		this.name = "jidi";
	}
	print(){
		console.info(this.name);
	}
}

class B extends A{
	name = "rycony";
	
	m() {
		super.print(); // super调用父类方法,方法内部this指向当前子类实例
	}
}

let b = new B();
b.m(); // "rycony"

上面例子中,子类B方法m()中通过super.print()调用了父类Aprint()方法,此时print()方法内部的this指向当前子类实例。

通过super对某个属性赋值,赋值的属性会变成子类实例的属性。

class A{
	constructor(){
		this.name = "jidi";
	}
}

class G extends A{
	constructor(){
		super();
		this.age = 21;
		super.age = 23; // 通过super给属性赋值
	}
	
	getAge(){
		super.age = 33; 
		console.info(super.age)
		return this.age;
	}
}

x = new G(); // {name: "jidi", age: 23}
x.getAge(); // undefined 33

上面例子中,子类G在构造方法和普通方法getAge中通过super.age为属性age赋值,赋值的属性age会成为子类实例的属性,相当于调用this.age赋值。但是,当通过super.age读取属性时,读取的是父类原型对象的属性,相当于A.prototype.age,所以为undefined

在子类的静态方法中通过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

上面例子中,静态方法B.m里面,super.print指向父类A的静态方法。这个方法里面的this指向的是子类B,不是子类B的实例。

使用super关键字,必须显式指定是作为函数、还是作为对象调用,否则会报错。

class A {}

class B extends A{
	
	m(){
		console.log(super); // SyntaxError: 'super' keyword unexpected here
	}
}

5. 原生构造函数

ES6中的原生构造函数有:

  • Boolean()
  • Number()
  • String()
  • Array()
  • Date()
  • Function()
  • RegExp()
  • Error()
  • Object()

ES6 允许继承原生构造函数自定义子类。

class MyFirstArray extends Array {
	constructor(...args){
		super(...args);
	}
	getName() {
		return "jidi";
	}
}

let x = new MyFirstArray();

x[0] = 1;
x[1] = 2;

x.length; // 2
x.getName(); // "jidi"
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章