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"
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章