JS是一門面向對象的語言,可是在JS中沒有引入類的概念,以前特別疑惑在JS中繼承的機制究竟是怎樣的,一直學了JS的繼承這塊後才恍然大悟,遂記之。數組
假如如今有一個「人類」的構造函數:app
咱們能夠經過在子類型的內部調用超類型的構造函數來達到子類型繼承超類型的效果。函數是在特定環境中執行的代碼對象,所以能夠經過call()或者apply()在新建立的對象上執行構造函數。函數
不過借用構造函數進行繼承,不免會有方法都在構造函數中定義,沒法實現函數的複用。而且在超類型的原型中定義的方法對於子類型是不可見的,結果全部類型都要使用構造函數進行繼承,因此單獨使用構造函數的狀況比較少。this
原型鏈的定義機制我在JavaScript中原型鏈的那些事中已經提過了,說到底,經過原型鏈實現繼承根本是經過prototype屬性進行實現的。spa
若是Man的原型指向的是Human的實例,那麼Man原型對象中將會包括一個指向Human的指針,那麼全部Man的實例就均可以繼承Human了。.net
咱們改變了Man的prototype的指向,讓他等於一個Human的實例對象。即:指針
組合繼承的總體思想就是將原型鏈和借用構造函數同時使用,取二者的長處的一種繼承模式。思路是使用原型鏈實現原型屬性和方法的繼承,借用構造函數來實現對實例屬性的繼承。這樣作的好處是實現了函數的複用,同時又保證了每一個屬性都有本身的屬性。code
那麼上面讓「男人」繼承「人類」就能夠經過組合繼承實現:對象
若是說繼承的對象並非構造函數呢?咱們沒有辦法使用借用構造函數進行繼承,這個時候咱們就可使用原型式繼承。
這個繼承模式是由道格拉斯·克羅克福德提出的。原型式繼承並無使用嚴格意義上的構造函數。而是藉助原型能夠在已有的對象上建立新的對象,同時還避免了建立自定義類型。因此道格拉斯·克羅克福德給出了一個函數:
咱們能夠建立一個新的臨時性的對象來保存超類型上全部屬性和方法,用來給子類型的繼承。而這個就是這個函數要作的事。
在object函數內部先建立了一個臨時性的構造函數,而後將傳入的對象做爲這個構造函數的原型,最後返回這個臨時類型的新實例。本質上其實就是object對傳入的對象進行了一次淺複製。
如今有一個「男人」對象:
這裏須要注意的是,這兩個對象如今時普通的對象,而不是構造函數,因此咱們沒法使用上面的方法。
咱們能夠經過原型式繼承,以下面的例子:
在ECMAScript中經過函數Object.create()規範了原型式繼承,該方法接收兩個參數,一個用於新對象原型的對象,第二個參數用於爲新對象定義額外屬性的對象,在傳入一個參數的狀況下,Object.create()和上面的object()函數做用相同。
咱們能夠想一下其實繼承的意思就是子類型把超類型的全部屬性和方法拿過來放在本身身上。 那麼咱們能夠將超類型的屬性和方法所有拷貝給子類型,從而實現繼承。
咱們能夠實現一個方法,將超類型的對象傳入方法,而後將對象的屬性添加到子類型上並返回,具體代碼以下:
具體使用能夠這樣:
使用方法相似於上面介紹的原型式繼承。可是這樣實現繼承有一個很大的問題,那就是當對象的屬性是引用類型值(數組,對象等)時,在拷貝過程當中,子對象得到的只是一個內存地址,而不是真正的屬性拷貝。
咱們能夠在淺拷貝的基礎上進行深拷貝。咱們知道,當在拷貝基本類型值時是在內存中新開闢了一塊區域用於拷貝對象屬性的存儲,因此咱們只須要遞歸下去調用淺拷貝就好了。
使用方法和淺拷貝相似,這裏就不舉例了。
寄生式繼承的思路就是建立一個用於封裝繼承過程的函數,在該函數內部以某種方式來加強對象,最後再向真的它作了全部工做同樣返回對象。仍是上面man和human兩個對象間實現繼承的例子:
在主要實現對象是自定義類型而不是構造函數的狀況下,寄生式繼承是一種有用的繼承模式,其中使用的object函數不是必須的,任何能實現該功能的函數均可以。
組合繼承是JS中一種很是經常使用的繼承模式,但是這個方式實現繼承有一個問題,就是不管在任何狀況下,都會調用兩次超類型構造函數。一次是在建立子類型原型的時候,第二次是在子類型構造函數內部。子類型最終會包含超類型對象的所有實例屬性,可是咱們不得不在調用子類型構造函數時重寫這些屬性。如此在繼承很是頻繁的狀況下就會形成內存過分損耗的狀況了。這個時候,咱們可使用寄生組合式繼承!
寄生組合式繼承,就是借用構造函數來繼承屬性,經過原型鏈的混成形式來繼承方法。具體思路是沒必要爲了指定子類型的原型而調用超類型的構造函數,咱們所須要的無非就是超類型原型的一個副本而已,本質上,就是使用寄生式繼承來繼承超類型的原型,而後再將結果指定給子類型的原型。
基本模式以下:
讓咱們回到第一個問題:有一個「男人」的構造函數和「人類」的構造函數,我如今想讓男人繼承人類!
以上~