首頁  >  文章  >  web前端  >  阿里巴巴技術文章分享 Javascript繼承機制的實作_javascript技巧

阿里巴巴技術文章分享 Javascript繼承機制的實作_javascript技巧

WBOY
WBOY原創
2016-05-16 15:20:011089瀏覽

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 Dickens
console.log( employee.summary() ); //Bob Ray, Alibaba
console.log( manager.summary() ); //Calvin Klein, Alibaba, Senior Manager

この記事は良いので、いいね!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn