能夠寫出可維護的物件導向JavaScript程式碼不僅可以節省金錢,還能讓你很受歡迎。不相信?有可能你自己或其他什麼人有一天會回來重複使用你的程式碼。如果能盡量讓這個經歷不那麼痛苦,就可以省下不少時間。地球人都知道,時間就是金錢。同樣的,你也會因為幫某人省去了頭痛的過程而獲得他的偏好。但是,在開始探索如何編寫可維護的物件導向JavaScript程式碼之前,我們先來快速看看什麼是物件導向。如果已經了解物件導向的概念了,就可以直接跳過下一節。
什麼是面向對象?
物件導向程式設計主要透過程式碼代表現實世界中的實質物件。要建立對象,首先需要寫一個「類別」來定義。 類幾乎可以代表所有的東西:帳戶,員工,導航菜單,汽車,植物,廣告,飲料,等等。而每次要創建物件的時候,就從類別實例化一個物件。換句話說,就是創建類別的實例做為物件。事實上,通常處理一個以上的同類事物時就會使用到物件。另外,只需要簡單的函數式程式就可以做的很好。對象實質上是資料的容器。因此在一個employee物件中,你可能要儲存員工號,姓名,入職日期,職稱,工資,資歷,等等。物件也包括處理資料的函數(也稱為“方法”)。方法被用作媒介來確保資料的完整性,以及在儲存之前對資料進行轉換。例如,方法可以接收任意格式的日期然後在儲存之前將其轉換成標準化格式。最後,類別還可以繼承其他的類別。繼承可以讓你在不同類別中重複使用相同程式碼。例如,銀行帳戶和音像店帳戶都可以繼承一個基本的帳戶類,裡麵包括個人信息,開戶日期,分部信息,等等。然後每個都可以定義自己的交易或借款處理等資料結構和方法。
警告:JavaScript物件導向是不一樣的
在上一節中,概述了經典的物件導向程式設計的基本知識。說經典是因為JavaScript並不遵守這些規則。相反地,JavaScript的類別是寫成函數的樣子,而繼承則是透過原型實現的。原型繼承基本上意味著使用原型屬性來實現物件的繼承,而不是從類別繼承類別。
物件的實例化
以下是JavaScript中物件實例化的範例:
// 定義Employee類別
function Employee(num, fname, lname) {
this (Full) {
〠 + lname ;
}
};
// 實例化Employee物件
var john = new Employee("4815162342"" ull name is " + john.getFullName() );
在這裡,有三個重點要注意:
1 “class”函數名的第一個字母要大寫。這表明該函數的目的是被實例化而不是像一般函數一樣被呼叫。
2 在實例化的時候使用了new操作符。如果省略掉new而只呼叫函數則會產生很多問題。
3 因為getFullName指定給this運算子了,所以是公用的,但是fname和lname則不是。由Employee函數產生的閉包給了getFullName到fname和lname的入口,但同時對於其他類別仍然是私有的。
原型繼承
下面是JavaScript中原型繼承的例子:
// 定義Human類
function Human() {
this.setName = function (fname, lname) {
this.fname = fname;
this.lname = lname ;
}
this.getFullName = function () {
return this.fname + " " + this.lname;
this.getNum = function ( ) {
return num;
}
};
//讓Employee繼承Human類別
Employee.prototype = new Human();
);
john.setName("John", "Doe");
alert(john.getFullName() + "'s employee number is " + john.getNum());
這次,創建的Human類別包含人類的一切共有屬性-我也將fname和lname放進去了,因為不只是員工才有名字,所有人都有名字。然後將Human物件賦值給它的prototype屬性。
透過繼承實現程式碼重用
在前面的例子中,原來的Employee類別被分解成兩個部分。所有的人類通用屬性被移到了Human類別中,然後讓Employee繼承Human。這樣的話,Human裡面的屬性就可以被其他的物件使用,例如Student(學生),Client(顧客),Citizen(公民),Visitor(遊客),等等。現在你可能注意到了,這是分割和重複使用程式碼很好的方式。處理Human物件時,只需要繼承Human來使用已存在的屬性,而不需要對每種不同的物件都重新一一建立。除此之外,如果要新增一個「中間名字」的屬性,只需要加一次,那些繼承了 Human 類別的就可以立刻使用了。反而言之,如果我們只是想要給一個物件加上「中間名字」的屬性,我們就直接加在那個物件裡面,而不需要在Human 類別裡面加。
1. Public(公有的)和Private(私有的)
接下來的主題,我想談談類別中的公有和私有變數。根據物件中處理資料的方式不同,資料會被處理為私有的或公有的。私有屬性並不一定意味著其他人無法存取。可能只是某個方法需要用到。
● 只讀
有時,你只是想要在創建物件的時候能有一個值。一旦創建,就不想要其他人再改變這個值。為了做到這一點,可以創建一個私有變量,在實例化的時候給它賦值。
function Animal(type) {
var data = [];
data['type'] = type;
var fluffy = new Animal('dog');
fluffy.getType(); // 回傳'dog'
在這個例子中,Animal類別中建立了一個本地數組data。當 Animal物件被實例化時,傳遞了一個type的值並將該值放置在data數組中。因為它是私有的,所以該值無法被覆蓋(Animal函數定義了它的範圍)。一旦物件被實例化了,讀取type值的唯一方法是呼叫getType方法。因為getType是在Animal中定義的,因此憑藉Animal產生的閉包,getType可以進到data中。這樣的話,雖可以讀到物件的類型卻無法改變。
有一點非常重要,就是當物件被繼承時,「只讀」技術就無法運用。在執行繼承後,每個實例化的物件都會共用那些只讀變數並覆寫其值。最簡單的解決方法是將類別中的唯讀變數轉換成公共變數。但是你必須保持它們是私有的,你可以使用Philippe在評論中提到的技術。
● Public(公有)
當然也有些時候你想要任意讀寫某個屬性的值。要實現這一點,需要使用this操作符。
function Animal() {
this.mood = '';
}
var fluffy = new Animal();
fluffy.mood = 'happy';類別公開了一個叫mood的屬性,可以被隨意讀寫。同樣地,你也可以將函數指定給公有的屬性,例如先前範例中的getType函數。只是要注意不要給getType賦值,不然的話你會毀了它的。
完全私有
最後,可能你發現你需要一個完全私有化的本地變數。這樣的話,你可以使用與第一個例子中一樣的模式而不需要創建公有方法。
function Animal() {
var secret = "You'll never know!"
}
var fluffy = new Animal();產品需求變化同步,我們需要保持程式碼不過時。如果你已經做過某些專案或是長期維護過某個產品,那麼你就應該知道需求是改變的。這是一個不爭的事實。如果你不是這麼想的話,那麼你的程式碼在還沒寫之前就注定要荒廢。可能你突然需要將選項卡中的內容弄成動畫形式,或是需要透過Ajax呼叫來取得資料。儘管準確預測未來是不大可能,但是卻完全可以將程式碼寫靈活以備將來不時之需。
● Saner參數清單
在設計參數清單的時候可以讓程式碼有前瞻性。參數清單是讓別人實現你程式碼的主要接觸點,如果沒有設計好的話,是會很有問題的。你應該避免下面這樣的參數清單:
function Person(employeeId, fname, lname, tel, fax, email, email2, dob) {
};
這個類別十分脆弱。如果在你發布程式碼後想要加入一個中間名參數,因為順序問題,你不得不在列表的最後往上加。這讓工作變得尷尬。如果你沒有為每個參數賦值的話,將會十分困難。例如:
var ara = new Person(1234, "Ara", "Pehlivanian", "514-555-1234", null, null, null, "1976-05-17");
操作參數清單更整潔也更靈活的方式是使用這個模式:
function Person(employeeId, data) {
};
有第一個參數因為這是必要的。剩下的就混在對象的裡面,這樣才可以靈活運用。
var ara = new Person(1234, {
fname: "Ara",
lname: "Pehlivanian",
tel: "514-55-1235" dob12
});
這個模式的漂亮之處在於它即方便閱讀又高度靈活。注意到fax, email和email2完全被忽略了。不僅如此,物件是沒有特定順序的,因此哪裡方便就在哪裡添加一個中間名參數是非常容易的:
var ara = new Person(1234, {
fname: "Ara",
mname: "Chris",
lname: "Pehlivanian",
tel: "514-555-1234",
dob: "1976-05-17"
});
);
function Person(employeeId, data) {
this.fname = data['fname'];
};
如果data['fname'] 回傳一個值,那麼他就被設定好了。否則的話,沒被設定好,也沒有什麼損失。
● 讓程式碼可嵌入
隨著時間流逝,產品需求可能對你類別的行為有更多的要求。而該行為卻與你類別的核心功能沒有半毛錢關係。也有可能是類別的唯一一種實現,好比在一個選項卡的面板獲取另一個選項卡的外部資料時,將這個選項卡面板中的內容變灰。你可能想把這些功能放在類別的裡面,但是它們不屬於那裡。選項卡條的責任在於管理選項卡。動畫和取得資料是完全不同的兩碼事,也必須與選項卡條的程式碼分開。唯一一個讓你的選項卡條不過時而又將那些額外的功能排除在外的方法是,允許將行為嵌入到程式碼當中。換句話說,透過創建事件,讓它們在你的程式碼中與關鍵時刻掛鉤,例如onTabChange, afterTabChange, onShowPanel, afterShowPanel等等。那樣的話,他們可以輕易地與你的onShowPanel事件掛鉤,寫一個將面板內容變灰的處理器,這樣就皆大歡喜了。 JavaScript函式庫讓你可以夠容易做到這一點,但你自己寫也不那麼難。以下是使用YUI 3的一個範例。
});
🜎這個例子有一個簡單的 TabStrip 類,其中有一個showPanel方法。這個方法激發兩個事件,onShowPanel和afterShowPanel。這個能力是透過Y.EventTarget擴大類別來實現的。一旦做成,我們就實例化了一個TabStrip對象,並將一堆處理器都分配給它。這是用來處理實例的唯一行為而又能避免混亂當前類別的常用程式碼。
總結
如果你打算重用程式碼,無論是在同一網頁,同一網站還是跨專案操作,考慮一下在類別裡面將其打包和組織起來。物件導向JavaScript自然地幫助實作更好的程式碼組織以及程式碼重複使用。除此之外,有點遠見的你可以確保程式碼具有足夠的靈活性,可以在你寫完程式碼後持續使用很長時間。寫可重複使用的不過時JavaScript程式碼可以節省你,你的團隊還有你公司的時間和金錢。這絕對能讓你大受歡迎。