ES5和ES6中對繼承的實現

Realucas發表於2019-05-13

1.原型鏈相關知識

  正式學習之前,我們先溫習下原型鏈的相關知識:(圖片來源:Javascript的原型鏈圖

這裡寫圖片描述

  圖片中有待補充的內容包括:每一個new出來的物件或者函式有一個constructor 屬性指向建構函式,例子如下:

var b = new Function();
b.constructor === Function;  //true
複製程式碼

  先對照圖片對下面的例子做出解釋

var a = {};
console.log(a.prototype);  //=> undefined (例項化出來的物件)

var b = function(){};
console.log(b.prototype);  //=> {} (對應Foo.prototype)

var c = 'Hello';
console.log(c.prototype);  //=> undefined (例項出來的字串)
複製程式碼

補充知識點:

var o1 = new Object();
typeof(o1);      //=> 'object'

var o2 = {};
typeof(o2);     //=> 'object'

typeof(Object); //=> 'function'
複製程式碼

  我們不難得出一個結論,typeof所的到的物件型別和物件的__proto__型別相同,Function.prototype除外。

typeof Function.prototype.__proto__ ;  //=> 'object'
複製程式碼

2.ES5中的繼承

(相關閱讀:ES5和ES6中的繼承/ECMAScript 繼承機制實現

2.1 原型鏈方法

將父類例項當做子類建構函式的原型

//先來個父類,帶些屬性
function Super(){
    this.flag = true;
}
//為了提高複用性,方法繫結在父類原型屬性上
Super.prototype.getFlag = function(){
    return this.flag;
}
//來個子類
function Sub(){
    this.subFlag = false;
}
//實現繼承
Sub.prototype = new Super;
//給子類新增子類特有的方法,注意順序要在繼承之後
Sub.prototype.getSubFlag = function(){
    return this.subFlag;
}
//構造例項
var es5 = new Sub;
複製程式碼

優點:

  • 父類的方法(getName)得到了複用。

缺點:

  • 原型中屬性的改變會反應到所有的例項上
  • 建立子類的例項時,不能向父類的建構函式傳遞引數

  例子如下:

function Super(){
    this.flag = true;
}//父類
function Sub(){
   this.subFlag = false;
}//子類
Sub.prototype = new Super();//繼承
var obj = new Sub();//子類例項1
obj.flag = flase;  //修改之後,由於是原型上的屬性,之後建立的所有例項都會受到影響
var obj_2 = new Sub();//子類例項2
console.log(obj.flag)  //false複製程式碼

2.2 call和apply方法(和物件冒充類似)

call方法,它的第一個引數用作 this 的物件。其他引數都直接傳遞給函式自身。

function sayColor(sPrefix,sSuffix) {
    alert(sPrefix + this.color + sSuffix);
};

var obj = new Object();
obj.color = "blue";

sayColor.call(obj, "The color is ", "a very nice color indeed.");
複製程式碼

  在這個例子中,函式 sayColor()在物件外定義,即使它不屬於任何物件,也可以引用關鍵字 this。物件 objcolor屬性等於blue。呼叫call() 方法時,第一個引數是 obj,說明應該賦予sayColor() 函式中的 this 關鍵字值是 obj。第二個和第三個引數是字串。它們與 sayColor() 函式中的引數sPrefixsSuffix 匹配,最後生成的訊息 "The color is blue, a very nice color indeed." 將被顯示出來。

優點:

  • 子類的每個例項都有自己的屬性(name),不會相互影響。

缺點:

  • 父類方法沒有得到複用,方法存在於每個例項當中而不是來自繼承。

2.3 混合方式

function A (color){
	this.color = color
}
A.prototype.sayColor = function(){
	console.log(this.color)
}
function B(sColor,name){
	A.call(this,sColor);
	this.name = name;
}
B.prototype = new A();
B.prototype.sayName = function(){
	console.log(this.name)
}
var b = new B("red","nik")
複製程式碼

請注意:
在執行 B.prototype = new A(); 之後,B.prototype.constructor 指向A 。然而constructor的定義是要指向原型屬性對應的建構函式的,B.prototypeB建構函式的原型,所以應該新增一句糾正:

B.prototype.constructor = B;
複製程式碼

2 ES6中的繼承

  相關閱讀(Class的繼承

2.1 簡介

  Class可以通過extends關鍵字實現繼承,這比 ES5 的通過修改原型鏈實現繼承,要清晰和方便很多。

class Point {
}

class ColorPoint extends Point {
}
複製程式碼

   以上程式碼實際等同於下面的程式碼:

class ColorPoint extends Point {
}

// 等同於
class ColorPoint extends Point {
  constructor(...args) {
    super(...args);
  }
}
複製程式碼

  上面程式碼中,constructor方法和toString方法之中,都出現了super關鍵字,它在這裡表示父類的建構函式,用來新建父類的this物件。

  子類必須在constructor方法中呼叫super方法,否則新建例項時會報錯。這是因為子類沒有自己的this物件,而是繼承父類的this物件,然後對其進行加工。如果不呼叫super方法,子類就得不到this物件。

  **注意:**在子類的建構函式中,只有呼叫super之後,才可以使用this關鍵字,否則會報錯。這是因為子類例項的構建,是基於對父類例項加工,只有super方法才能返回父類例項。

2.2 super 關鍵字

  super這個關鍵字,既可以當作函式使用,也可以當作物件使用。在這兩種情況下,它的用法完全不同。

  第一種情況,super作為函式呼叫時,代表父類的建構函式。ES6 要求,子類的建構函式必須執行一次super函式。

  super雖然代表了父類A的建構函式,但是返回的是子類B的例項,即super內部的this指的是B,因此super()在這裡相當於A.prototype.constructor.call(this)

class A {
  constructor() {
    console.log(new.target.name);
  }
}
class B extends A {
  constructor() {
    super();
  }
}
new A() // A
new B() // B
複製程式碼

  上面程式碼中,new.target指向當前正在執行的函式。可以看到,在super()執行時,它指向的是子類B的建構函式,而不是父類A的建構函式。也就是說,super()內部的this指向的是B

  第二種情況,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指向子類。

class A {
  constructor() {
    this.x = 1;
  }
  print() {
    console.log(this.x);
  }
}

class B extends A {
  constructor() {
    super();
    this.x = 2;
  }
  m() {
    super.print();
  }
}

let b = new B();
b.m() // 2
複製程式碼

待補充。。。

相關文章