天天看點

js--如何實作繼承?

前言

  學習過 java 的同學應該都知道,常見的繼承有接口繼承和實作繼承,接口繼承隻需要繼承父類的方法簽名,實作繼承則繼承父類的實際的方法,js 中主要依靠原型鍊來實作繼承,無法做接口繼承。

  學習 js 繼承之前,我們需要了解原型這一 概念,我們知道 js 中建立對象通過構造函數來建立,而每一個構造函數都有對應的 prototype 的屬性,該屬性對應的值為一個對象,這個對象也就是所有通過該構造函數建立出來的執行個體所共享的屬性和方法,而建立出來的每一個執行個體對象都有一個指針指向這些共享的屬性和方法,這個指針就是所說的 __proto__(注意這裡是雙下劃線),是以就産生了三種來擷取原型的方法,分别是 p.__proto__,p.constructor.prototype,Object.getPrototypeOf( p ),這就是我對原型的了解。

  當我們在通路一個對象的屬性時,如果這個對象内部不存在這個屬性,那麼它就會在它的原型對象裡找這個屬性,這個原型對象又會有自己的原型,于是這樣一層一層向上找下去,也就産生了原型鍊的概念。原型鍊的盡頭就是 object.prototype ,是以我們每建立的一個對象都有 toString(),valueOf() 等方法的原因。

  有了上面的基礎常識作為鋪墊,我們來看下 js 中具體怎麼來實作繼承。

正文

  js 中實作繼承的方法有6種,具體實作如下:

  (1)原型鍊實作繼承

  上面的代碼需要注意必須在繼承父類語句之後才能在其原型上添加新的方法或者重寫父類的方法,同時添加新的方法的時候不能使用字面量的形式添加。

  所有的函數的預設原型都是 object,預設原型都會包含一個内部指針指向 object.prototype ,是以所有自定義的對象都有 toString()方法和 valueOf() 方法。

  确定原型和執行個體的關系的方法可以使用:instanceof 和 isPrototypeOf。

  優缺點:上面的方法讓新執行個體的原型等于父類的執行個體實作了原型鍊的繼承,子類的執行個體能夠繼承構造函數的屬性,構造函數的方法,父類的構造函數的屬性以及父類原型上的方法,但是新執行個體無法向構造函數傳參,繼承單一,所有的新執行個體都會共享父類構造函數的屬性,是以在父類構造函數種定義一個引用資料類型的時候,每個字類的執行個體都有擁有該引用類型的屬性,當其中一個執行個體對該屬性做了修改,别的執行個體也會收到影響。例子如下:

  (2)借用構造函數實作繼承

  上面的方法借用構造函數實作繼承,主要是用 call() 或者apply() 在子類的構造函數内部調用父類的構造函數,就相當于在子類構造函數内部做了父類函數的複制并且自執行。

  優缺點:通過構造函數實作繼承,隻能繼承父類構造函數的屬性,不能繼承父類原型上面的方法,無法實作構造函數的複用,每次用每次都要重新調用,相當于每個新執行個體都有父類構造函數的副本,造成臃腫,但是這種方法能夠解決原型鍊不能傳參的問題,對父類構造函數種屬性為引用資料類型的問題,以及通過多個 call 解決單一繼承問題等。

   (3)原型鍊和構造函數組合實作繼承(常用)

  上面的代碼使用原型鍊和構造函數組合實作了繼承,其中通過原型鍊實作對原型的屬性和方法的繼承,通過借用構造函數來實作對執行個體屬性的繼承,這樣即保證了函數的調用,有實作了每個執行個體都有自己的屬性,解決了執行個體中屬性幹擾的問題。

  優缺點:這種方法結合了前兩種模式的優點,達到了傳參和複用的效果,可以繼承父類原型的屬性和方法,可以傳參,可以複用,同時每個新執行個體引入的構造函數的屬性都是私有的,但是實作需要調用兩次父類構造函數,這樣就存在記憶體消耗問題,子類的構造函數會代替原型上的那個父類構造函數。

   (4)原型式實作繼承

  上面的代碼重點在于在 subFun() 函數内部建立一個臨時性的構造函數,然後将傳入的對象作為這個構造函數的原型,最後傳回這個臨時類型的一個新執行個體,相當于用一個函數包裝了一個對象,然後傳回這個函數的的調用,這個函數會就程式設計了可以随意增添屬性的執行個體或者對象, object.create() 就是這個原理。es5中object.create() 接受兩個參數,一個參數作為新對象原型的對象,另一個可選參數作為新對象定義額外屬性的對象,當兩個參數都存在的時候,任何屬性都會覆寫原型對象上的同名屬性。

  優缺點:這種方法類似于複制一個對象,用函數來包裝,其實就是哪一個對象作為繼承,然後傳入另一個對象,本質就是對傳入的對象進行一次淺拷貝,但是所有執行個體都會繼承原型上的屬性,且無法實作複用,若包含引用資料類型始終會共享相應的值。

   (5)寄生式實作繼承

  上面的代碼對比原型式繼承,其實就是在原型式繼承的基礎上套了一層殼子,建立了一個僅用于封裝繼承過程的函數,該函數在内部以某種方式來增強對象,最後再傳回一個對象。

  優缺點:這種方法沒有建立自定義類型,因為隻是給傳回的對象添加了一層殼子,實作了建立的新對象,但是這種方法沒有用到原型,無法實作複用。

  (5)寄生組合式實作實作繼承(常用)

  針對組合實作繼承存在的問題進行了優化,前面說到組合繼承要調用兩次父類構造函數,第一次是在建立子類原型的時候,第二次是在子類構造函數内部 call 調用。對于這兩次調用,第一次調用父類是可以避免的,不必為了指定子類型的原型而調用夫類型的構造函數,我們無非是需要一個父類型原型的一個副本而已。

  上面的方法是 js 中實作繼承最常見方法,它完美解決了組合式繼承的中兩次調用父類原型的bug,通過寄生,在函數内部傳回對象然後調用,使用組合,使得函數的原型等于另一個執行個體,在函數中調用 call 引入另一個構造函數,實作了可以傳參的功能,避免了在父類原型上建立不必要的屬性,成為最理想的實作繼承的方法。需要注意  inheritObject() 函數接受兩個參數,分别式子類和父類的兩個構造函數。

  優缺點:使用寄生式繼承實作了繼承父類的原型,然後再将結果指定給子類型的原型。使用組合繼承得到傳參複用等效果。

總結

  以上就是本文的全部内容,希望給讀者帶來些許的幫助和進步,友善的話點個關注,小白的成長之路會持續更新一些工作中常見的問題和技術點。

js--如何實作繼承?