javascript作為一門腳本語言,在設計之初并沒有考慮到面向對象的特性。即便到了當今這個遍布現代浏覽器的年代,各種javascript
架構/庫如雨後春筍般地瘋狂生長,javascript中連個 class
關鍵字都沒有。如果你要編寫一個類,你還得借助于function,至于繼承、重載什麼的,就别奢望了。
可是,沒有繼承,日子怎麼過啊?難道把所有的共有邏輯都拷貝一遍,實作最低級的代碼複用?
答案當然是——no,是以,我們要自己實作繼承!
最關鍵的目标當然是繼承——子類自動擁有父類的所有公共屬性和方法。
支援instanceof,例如c是子類的執行個體,而p是父類,c instanceof p應該傳回true。
其次應該能夠重寫(override)父類的方法,并且在子類的方法中,能夠友善地調用到父類的同名方法。
至于說重載(overload),由于javascript的語言特性(不可以有同名方法,即便它們參數清單不一樣),無法實作。
javascript的對象有一個很重要的屬性——__proto__,也就是原型。原型實質上也是一個對象,所有它也可以有自己的原型,這樣就形成一個原型鍊。當你調用某個對象的某個方法,或者讀取該對象的某個屬性,javascript執行器是這樣做的:
首先到該對象中找對應的方法或屬性,如果找不到,
到該對象的原型中找,如果還找不到,
到原型的原型裡面找
...
直到最後找到object的原型為止,如果還沒有則傳回undefined
如下圖所示:
原型鍊的這個特性,和繼承很相似,是以自然而然,我們可以利用它來實作繼承機制。而原型鍊對instanceof的支援,使得它成為很好的選擇。
我們定義extend函數,這個函數接受兩個參數,第一個是父類,第二個是子類,如下所示:
這個函數對子類進行處理,并傳回子類。處理的邏輯如下:
通過将子類的原型鍊與父類的原型鍊連接配接起來,子類可以自動擁有父類的方法和屬性:
為了連接配接原型鍊,需要建立一個父類的執行個體,并将其賦給子類的原型屬性。但我們不希望在extend方法裡面就執行個體化父類,是以引入了一個中間類t,以解決這個問題。
原型鍊建立之後,原來子類原型上的方法和屬性我們也需要保留下來:
如果父類有同名方法,我們使用一個閉包,來保留對父類方法和子類方法的引用。然後,修改新的原型中該方法的引用,将其指向一個新的
function。在這個function裡面,我們建立一個臨時屬性super,将其指向父類方法,并調用子類方法,這樣在子類方法中,通過
this.super可以調用該父類方法:
對于屬性,不存在重寫的問題,是以直接将子類原來的原型中的屬性加到新的原型中即可:
為了讓子類能夠通路到父類的構造器,我們将父類賦給子類的super屬性:
假設我們要設計一個管理系統,裡面涉及到客戶、勞工和經理等。将客戶和員工的共性抽象出來,我們得到人(people);然後将勞工和經理的共性抽象得到員工(employee)。這樣我們得到三級類結構:
實作這個設計的代碼如下:
我們希望對每個人都有一個描述,people是姓+名;員工在姓+名之後,還包括公司名稱;而經理在員工的描述之後,還包括職位。代碼如下:
在所有的成員方法都已經定義好之後,聲明類的繼承(必須先定義方法,再聲明類的繼承,否則無法在方法中使用this.super調用父類方法!):
使用這些類就比較簡單,直接new就好了: