JavaScript 繼承

繼承

這裏是 JavaScript 中比較有趣的一個部分了,首先要理清楚 對象、構造函數、原型對象 這幾個概念和它們之間的關係,具體能夠參考前面的 02.建立對象。首先明確一點:JS沒法實現接口集成,只支持實現繼承。接下里咱們講講如何實現繼承。git

原型鏈

理解起來不難。首先咱們知道 構造函數有一個原型對象,而實例包含一個指向構造函數原型對象的指針,但這裏咱們只涉及到了一個原型對象,是不足以造成 的, 的表面意思是 一個原型對象緊接着連接另外一個原型對象,而照目前咱們的知識來看,構造函數的原型對象只包含 constructor指針和一些定義在原型上的屬性和方法,這裏面沒辦法找到另外一個原型,那麼若是 一個構造函數的原型對象是另外一個構造函數的實例呢? ,這樣,實例的內部屬性指向了構造函數的原型對象,而該原型對象是另外一個構造函數的實例,則該原型對象有一個指向了另外一個構造函數的原型對象的屬性,這樣咱們就將兩個原型對象串成了鏈。以上仍是隻有兩個原型對象,若是另外一個構造函數的原型對象又是其餘構造函數的實例呢?層層遞進的狀況下,咱們獲得了一條 由原型對象組成的鏈條原型鏈github

function SuperType () {
  this.isSuperType = true
}

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

function SubType () {
  this.isSubType = true
}
SubType.prototype = new SuperType()
SubType.prototype.getSubValue = function () {
  return this.isSubType
} 

let instance = new SubType()
複製代碼

以上代碼即實現了繼承,instance 實例也得到了一條原型鏈
bash

  1. SuperType 有一個原型對象,這個原型對象長這樣:
{
  constructor: SuperType,
  getSuperValue: (function)
}
複製代碼
  1. 咱們使用 new SuperType() 建立了一個實例,該實例天然有一個屬性 [[__proto_]] 指向了上述原型對象,同時咱們將 SubType 的原型對象設置爲該實例,此時,SubType 的原型對象長這樣:
{
  isSuperType: true,
  __proto__: {
    constructor: SuperType,
    getSuperValue: (function)
  }
}
複製代碼
  1. 咱們爲SubType賦值完原型對象以後,咱們還在其原型上添加了子類自定義的方法 getSubValue(),此時,SubType 的原型對象就長成了這樣:
{
  isSuperType: true,
  getSubType: (function),
  __proto__: {
    constructor: SuperType,
    getSuperValue: (function)
  }
}
複製代碼
  1. 咱們建立了SubType 的實例 instance,天然,instance 有一個指向了上述原型對象的屬性,那麼此時,instance 對象長這樣:
instance = {
  isSubType: true,
  __proto__: {
    isSuperType: true,
    getSubType: (function),
    __proto__: {
      constructor: SuperType,
      getSuperValue: (function),
      __proto__: {
        constructor: Object,
        hasOwnProperty: (function),
        isPrototypeOf: (function),
        propertyIsEnumerable: (function),
        toLocaleString: (function),
        toString: (function),
        valueOf: (function)
      }
    }
  }
}
複製代碼

這樣咱們就值觀地看見,instance的表示中存在了兩個 __proto__ 屬性,咱們將這樣產生的鏈就叫作原型鏈了,而後當咱們使用instance的一些屬性和方法時,會依據這條鏈去尋找同名的屬性來返回:函數

instance.isSubType // true
instance.isSuperType // 位於第一層 __proto__ 中,返回true
instance.getSubType() // 位於第一層 __proto__ 中,返回函數運行結果
instance.getSuperValue() // 位於第二層 __proto__ 中,返回函數運行結果
複製代碼

至此咱們就完成了:繼承,同時爲子類添加自定義的方法。
可是原型鏈繼承仍然有缺點:因爲全部子類實例都指向了同一個原型對象,而該原型對象時父類的實例,那麼該對象的實例的屬性被共享了,即出現了:ui

let instance1 = new SubType()
instance1.isSuperType === instance.isSuperType
複製代碼

這種狀況在上一小節中咱們也遇到了,顯然這樣是不太符合咱們的指望的,而解決這個問題的辦法也和上一小節有些相似。this

而咱們在操做原型的過程當中必定要注意保持住構造函數和原型對象之間的聯繫,一旦切斷了聯繫,後續建立的實例的 __proto__ 和先前建立的實例的 __proto__ 將再也不指向同一個原型對象。這個咱們在講原型的時候也提到過。spa

組合繼承

上面講到如何利用原型鏈的方式實現繼承,同時指出該方法存在了缺點,即子類指向的原型對象中的 屬性 是實例共享的。爲了解決這個問題咱們能夠利用屬性覆蓋解決。prototype

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

SuperType.prototype.sayName = function () {
  alert(this.name)
}

function SubType (name, age) {
  // 在這裏咱們繼承屬性,使得 實例上的屬性覆蓋掉原型對象上的屬性
  SuperType.call(this, name)
  this.age = age
}

SubType.prototype = new SuperType()
SubType.prototype.constructor = SubType
SubType.prototype.sayAge = function () {
  alert(this.age)
}

let instance1 = new SubType('Nicholad', 21)
let instance2 = new SubType('Bob', 22)
instance1.colors.push('black') // ['red', 'green', 'blue', 'black']
alert(instance2.colors) // ['red', 'green', 'blue']
複製代碼

這個方法很好地覆蓋了原型屬性,從而達到實例獨立,是較爲實用的方法。可是該方法仍然存在缺點:咱們實例化一次子類會調用兩次父類,第一次爲覆蓋圓形屬性時,第二次爲更改子類原型屬性時。指針

持續更新在githubcode

相關文章
相關標籤/搜索