學習原型鏈(二)繼承

直接利用原型鏈繼承

這是最簡單粗暴的繼承方式。讓構造函數 A 的原型對象是構造函數 B 的實例,即 A.prototype = new B()。這樣一來,A 的實例的原型就會指向 B 的實例,A 經過原型鏈就能訪問到 B 的屬性和方法。假設 B 的原型又是 C 的實例,即 B 繼承了 C,沿着原型鏈一直向上,就構成了實例與原型的鏈條,也實現了繼承。函數

function SuperType () {
  this.property = true;
}

SuperType.prototype.getSuperValue = function () {
  return this.property;
}

function SubType () {
  this.subproperty = false;
}

/* 繼承 SuperType */
SubType.prototype = new SuperType();

SubType.prototype.constructor = SubType;
SubType.prototype.getSubValue = function () {
  return this.subproperty;
}

var instance = new SubType();
console.log(instance.getSuperValue());  /* true */
複製代碼

上面的例子中,getSuperValue 定義在 SuperType 的原型中,而 property 定義在了 SuperType 的實例中。隨着繼承關係的實現, SuperType 的實例成爲了 SubType 的原型,property 也就定義在了 SubType 的原型中了。隨後,咱們將 SubType 原型的 constructor 修改成 Subtype,並在其中定義了 getSubValue 方法。SubType 的構造函數還在其實例中定義了 subproperty 屬性。工具

當咱們經過 instance 實例訪問 getSuperValue 函數時,查找順序爲:instance -> Subtype.prototype(也是SuperType的實例) -> SubType.prototype.__proto__(即SuperType.prototype),最終在 SuperType 的原型中找到了這個函數並調用。優化

這種方式存在與「利用原型模式建立對象」相似的問題:構造函數 SuperType 的實例成爲 SubType 的原型,那麼 SubType 的原型中存在 SuperType 的實例屬性,這些屬性將會被 SubType 的實例共享。ui

另外一個問題是,沒法向 SuperType 傳遞初始化參數。this

借用構造函數

也叫僞造對象或經典繼承,即在子類的構造函數中調用父類的構造函數。究其根源,也就是將父類構造函數定義屬性和函數的操做由子類構造函數完成,本來父類構造函數會在父類實例中定義這些屬性和函數,如今子類也在本身的實例中定義了這些屬性和函數。別忘了,做用域鏈上,子類會屏蔽對父類的同名屬性和函數的訪問。spa

function SuperType (name) {
  this.name = name;
  this.colors = ['red', 'blue', 'green'];
}

function SubType (name) {
  /* 繼承了 SuperType */
  SuperType.call(this, name);
  
  this.age = 27;
}

var instance1 = new SubType('Nocholas');
instance1.colors.push('black'); /* colors 是借用 SuperType() 定義在 instance1 上的實例屬性 */
console.log(instance1.colors);  /* 'red, blue, green, black' */

var instance2 = new SubType('Grey');
console.log(instance2.colors);  /* 'red, blue, green' */
console.log(instance2.name);    /* 'Grey' */
console.log(instance2.age);     /* 27 */
複製代碼

若是僅僅是借用構造函數,那麼也就沒法避免構造函數模式存在的問題 —— 函數都在構造函數中定義,沒法複用。prototype

組合繼承

組合繼承 = 原型鏈 + 借用構造函數code

  • 原型鏈:繼承父類的原型的屬性和函數
  • 借用構造函數:將父類構造函數定義的屬性在子類實例中定義
function SuperType (name) {
  this.name = name;
  this.colors = ['red', 'blue', 'green'];
}

Super.prototype.sayName = function () {
  console.log(this.name);
}

function SubType (name, age) {
  /* 繼承 SuperType 屬性 */
  SuperType.call(this, name);
  
  this.age = age;
}

/* 繼承 SuperType 方法 */
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function () {
  console.log(this.age);
}
複製代碼

咱們利用 SuperType.call(this, name)SuperType 的屬性定義到了 SubType 的實例中,也就不存在共享屬性的問題了;又利用 SubType.prototype = new SuperType()SubType 提供了 SuperType 原型中的函數。這樣一來,SuperType 的屬性和函數都獲得了繼承,且屬性不被共享、函數是共享的。對象

有一個小細節值得留意,因爲 SubType.prototypeSuperType 的實例,因此也會存在 namecolors 屬性,這是咱們使用原型鏈模式帶來的附屬產品。只不過因爲 SubType 實例中存在同名屬性,「屏蔽」了對 SubType.prototype.nameSubType.prototype.age 的訪問而已。後面咱們還會再優化它。繼承

原型式繼承

準確的講,我認爲「原型式繼承」只是新建了一個原型指向傳入對象的空白對象。

function object (o) {
  function F () {};
  F.prototype = o;
  return new F();
}
複製代碼

這種繼承方法雖然不會做爲直接可使用的繼承方式,但能夠做爲其餘繼承方式的一種工具。千萬不要忘記,o 做爲原型,其屬性是被共享的。

ES5 中規範化了原型式繼承 —— Object.create() 方法。它接收兩個參數,第一個參數與 object(o) 的參數相同;第二個參數可選,可爲返回對象定義新的屬性。

寄生式繼承

寄生式繼承的思路與寄生構造函數和工廠函數相似,即建立一個僅用於封裝繼承過程的函數,該函數在內部以某種方式來加強對象,最後再像是真地是它作了全部工做同樣返回對象。

function createAnother (original) {
  var clone = object(original);  /* 經過調用函數建立一個新對象 */
  clone.sayHi = function () {    /* 以某種方式來加強這個對象 */
    console.log('Hi');
  }
  return clone;                  /* 返回這個對象 */
}

var person = {
  name: 'Nicholas',
  friends: ['Shelby', 'Court', 'Van']
}

var anotherPerson = createAnother(person);
anotherPerson.sayHi();  /* 'Hi' */
複製代碼

anotherPersonperson 爲原型,具備 person 的全部屬性和函數,並且在實例上定義了本身的 sayHi 函數。

僅適用於不考慮自定義類型和構造函數的狀況下可使用這種模式。但因爲不能作到函數複用,因此還不是最完美的方式。

寄生組合式繼承

前面提到的組合繼承已是相對比較好的繼承方式了。但它調用了兩次 SuperType 構造函數,使得 SubType 原型和實例中擁有 namecolors 屬性的兩份拷貝。

咱們改進的思路是:

沒必要爲了指定 SubType 的原型而調用 SuperType 的構造函數,咱們須要的無非就是 SuperType 原型的一個副本而已。

咱們使用寄生式繼承來繼承 SuperType 的原型。

function inheritPrototype (subType, superType) {
  var prototype = object(superType.prototype);  /* 建立對象 */
  prototype.constructor = subType;              /* 加強對象 */
  subType.prototype = prototype;                /* 指定對象 */
}


function SuperType (name) {
  this.name = name;
  this.colors = ['red', 'blue', 'green'];
}

SuperType.prototype.sayName = function () {
  console.log(this.name);
}

function SubType (name, age) {
  SuperType.call(this, name);
  
  this.age = age;
}

inheritPrototype(SubType, SuperType);

SubType.prototype.sayAge = function () {
  console.log(this.age);
}
複製代碼

使用寄生組合式繼承,咱們既實現了原型鏈,又避免了組合繼承的兩個調用構造函數的問題。

相關文章
相關標籤/搜索