Javascript作為一門腳本語言,在設計之初并沒有考慮到面向對象的特性。即便到了當今這個遍布現代瀏覽器的年代,各種Javascript 框架/庫如雨后春筍般地瘋狂生長,Javascript中連個 class 關鍵字都沒有。如果你要編寫一個類,你還得借助于function,至于繼承、重載什么的,就別奢望了。
可是,沒有繼承,日子怎么過啊?難道把所有的共有邏輯都拷貝一遍,實現最低級的代碼復用?
答案當然是――NO,所以,我們要自己實現繼承!
目標
最關鍵的目標當然是繼承――子類自動擁有父類的所有公共屬性和方法。
支持instanceof,例如c是子類的實例,而P是父類,c instanceof P應該返回true。
其次應該能夠重寫(Override)父類的方法,并且在子類的方法中,能夠方便地調用到父類的同名方法。
至于說重載(Overload),由于Javascript的語言特性(不可以有同名方法,即便它們參數列表不一樣),無法實現。
設計與實現
Javascript的對象有一個很重要的屬性――__proto__,也就是原型。原型實質上也是一個對象,所有它也可以有自己的原型,這樣就形成一個原型鏈。當你調用某個對象的某個方法,或者讀取該對象的某個屬性,Javascript執行器是這樣做的:
1、首先到該對象中找對應的方法或屬性,如果找不到,
2、到該對象的原型中找,如果還找不到,
3、到原型的原型里面找
4、...
5、直到最后找到Object的原型為止,如果還沒有則返回undefined
如下圖所示:

原型鏈的這個特性,和繼承很相似,所以自然而然,我們可以利用它來實現繼承機制。而原型鏈對instanceof的支持,使得它成為很好的選擇。
我們定義extend函數,這個函數接受兩個參數,第一個是父類,第二個是子類,如下所示:
function extend(ParentClass, ChildClass) {  ...  return ChildClass;}這個函數對子類進行處理,并返回子類。處理的邏輯如下:
建立原型鏈
通過將子類的原型鏈與父類的原型鏈連接起來,子類可以自動擁有父類的方法和屬性:
var pp = ParentClass.prototype,  cp = ChildClass.prototype;function T() {};T.prototype = pp;ChildClass.prototype = new T();為了連接原型鏈,需要創建一個父類的實例,并將其賦給子類的原型屬性。但我們不希望在extend方法里面就實例化父類,所以引入了一個中間類T,以解決這個問題。
實現重寫
原型鏈建立之后,原來子類原型上的方法和屬性我們也需要保留下來:
方法
如果父類有同名方法,我們使用一個閉包,來保留對父類方法和子類方法的引用。然后,修改新的原型中該方法的引用,將其指向一個新的 function。在這個function里面,我們創建一個臨時屬性super,將其指向父類方法,并調用子類方法,這樣在子類方法中,通過 this.super可以調用該父類方法:
ChildClass.prototype[name] = (function(pm, cm) {  return function() {    var _super = this.super;    this.super = pm;    var result = cm.apply(this, arguments);    this.super = _super;    return result;  };})(pp[name], cp[name]);屬性
對于屬性,不存在重寫的問題,所以直接將子類原來的原型中的屬性加到新的原型中即可:
ChildClass.prototype[name] = cp[name];
構造器
為了讓子類能夠訪問到父類的構造器,我們將父類賦給子類的super屬性:
ChildClass.super = ParentClass;
如何使用
假設我們要設計一個管理系統,里面涉及到客戶、工人和經理等。將客戶和員工的共性抽象出來,我們得到人(People);然后將工人和經理的共性抽象得到員工(Employee)。這樣我們得到三級類結構:

實現這個設計的代碼如下:
function People(firstname, lastname) {  this.firstname = firstname;  this.lastname = lastname;}function Employee(firstname, lastname, company) {  Employee.super.apply(this, arguments);  this.company = company;}function Manager(firstname, lastname, company, title) {  Manager.super.apply(this, arguments);  this.title = title;}我們希望對每個人都有一個描述,People是姓+名;員工在姓+名之后,還包括公司名稱;而經理在員工的描述之后,還包括職位。代碼如下:
People.prototype.summary = function() {  return this.firstname + " " + this.lastname;};Employee.prototype.summary = function() {  return this.super.call(this) + ", " + this.company;};Manager.prototype.summary = function() {  return this.super.call(this) + ", " + this.title;};在所有的成員方法都已經定義好之后,聲明類的繼承(必須先定義方法,再聲明類的繼承,否則無法在方法中使用this.super調用父類方法!):
extend(People, Employee);extend(Employee, Manager);
使用這些類就比較簡單,直接new就好了:
var people = new People("Alice", "Dickens");var employee = new Employee("Bob", "Ray", "Alibaba");var manager = new Manager("Calvin", "Klein", "Alibaba", "Senior Manager");console.log( people.summary() ); //Alice Dickensconsole.log( employee.summary() ); //Bob Ray, Alibabaconsole.log( manager.summary() ); //Calvin Klein, Alibaba, Senior Manager這篇文章不錯吧,那就給個贊吧!
新聞熱點
疑難解答