如何編寫可維護的物件導向JavaScript代碼

來源:互聯網
上載者:User
 英文原文:How to Write Maintainable OO JavaScript Code

  能夠寫出可維護的物件導向 JavaScript 代碼不僅可以節約金錢,還能讓你很受歡迎。不信?有可能你自己或者其他什麼人有一天會回來重用你的代碼。如果能盡量讓這個經曆不那麼痛苦,就可以節省不少 時間。地球人都知道,時間就是金錢。同樣的,你也會因為幫某人省去了頭疼的過程而獲得他的偏愛。但是,在開始探索如何編寫可維護的物件導向 JavaScript 代碼之前,我們先來快速看看什麼是物件導向。如果已經瞭解物件導向的概念了,就可以直接跳過下一節。

  什麼是物件導向?

  物件導向編程主要通過代碼代表現實世界中的實質對象。要建立對象,首先需要寫一個“類”來定義。 類幾乎可以代表所有的東西:賬戶,員工,導航 菜單,汽車,植物,廣告,飲料,等等。而每次要建立對象的時候,就從類執行個體化一個對象。換句話說,就是建立類的執行個體做為對象。事實上,通常處理一個以上的 同類事物時就會使用到對象。另外,只需要簡單的函數式程式就可以做的很好。對象實質上是資料的容器。因此在一個 employee 對象中,你可能要儲存員工號,姓名,入職日期,職稱,工資,資曆,等等。對象也包括處理資料的函數(也叫做“方法”)。方法被用作媒介來確保資料的完整 性,以及在儲存之前對資料進行轉換。例如,方法可以接收任意格式的日期然後在儲存之前將其轉化成標準化格式。最後,類還可以繼承其他的類。繼承可以讓你在 不同類中重複使用相同代碼。例如,銀行賬戶和音像店賬戶都可以繼承一個基本的賬戶類,裡麵包括個人資訊,開戶日期,分部資訊,等等。然後每個都可以定義自 己的交易或者借款處理等資料結構和方法。

  警告:JavaScript 物件導向是不一樣的

  在上一節中,概述了經典的物件導向編程的基本知識。說經典是因為 JavaScript 並不遵循這些規則。相反地,JavaScript 的類是寫成函數的樣子,而繼承則是通過原型實現的。原型繼承基本上意味著使用原型屬性來實現對象的繼承,而不是從類繼承類。

  對象的執行個體化

  以下是 JavaScript 中對象執行個體化的例子:

// 定義 Employee 類
function Employee (num, fname, lname) {
this.getFullName = function () {
return fname + " " + lname;
}
};
// 執行個體化 Employee 對象
var john = new Employee ("4815162342", "John", "Doe");
alert ("The employee's full 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;
}
}

// 定義 Employee 類
function Employee (num) {
this.getNum = function () {
return num;
}
};
//讓 Employee 繼承 Human 類
Employee.prototype = new Human ();

// 執行個體化 Employee 對象
var john = new Employee ("4815162342");
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 類裡面加。

  Public(公有的)和 Private(私人的)

  接下來的主題,我想談談類中的公有和私人變數。根據對象中處理資料的方式不同,資料會被處理為私人的或者公有的。私人屬性並不一定意味著其他人無法訪問。可能只是某個方法需要用到。

  唯讀

  有時,你只是想要在建立對象的時候能有一個值。一旦建立,就不想要其他人再改變這個值。為了做到這點,可以建立一個私人變數,在執行個體化的時候給它賦值。

function Animal (type) {
var data = [];
data['type'] = type;
this.getType = function () {
return data['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';
fluffy.mood; // 返回 'happy'

  這次 Animal 類公開了一個叫 mood 的屬性,可以被隨意讀寫。同樣地,你還可以將函數指定給公有的屬性,例如之前例子中的 getType 函數。只是要注意不要給 getType 賦值,不然的話你會毀了它的。

  完全私人

  最後,可能你發現你需要一個完全私人化的本地變數。這樣的話,你可以使用與第一個例子中一樣的模式而不需要建立公有方法。

function Animal () {
var secret = "You'll never know!"
}

var fluffy = new Animal ();

  寫靈活的 API

  既然我們已經談到類的建立,為了保持與產品需求變化同步,我們需要保持代碼不過時。如果你已經做過某些項目或者是長期維護過某個產品,那麼你就 應該知道需求是變化的。這是一個不爭的事實。如果你不是這麼想的話,那麼你的代碼在還沒有寫之前就將註定荒廢。可能你突然就需要將選項卡中的內容弄成動畫 形式,或是需要通過 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-555-1234",
dob: "1976-05-17"
});

  這個模式的漂亮之處在於它即方便閱讀又高度靈活。注意到 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的一個例子。

<script type="text/javascript" src="http://yui.yahooapis.com/combo?3.2.0/build/
yui/yui-min.js"></script>
<script type="text/javascript">
YUI () .use ('event', function (Y) {
function TabStrip () {
this.showPanel = function () {
this.fire ('onShowPanel');
// 展現面板的代碼
this.fire ('afterShowPanel');
};
};

// 讓 TabStrip 有能力激發常用事件
Y.augment (TabStrip, Y.EventTarget);
var ts = new TabStrip ();
// 給 TabStrip 的這個執行個體建立常用時間處理器
ts.on ('onShowPanel', function () {
//在展示面板之前要做的事
});
ts.on ('onShowPanel', function () {
//在展示面板之前要做的其他事
});
ts.on ('afterShowPanel', function () {
//在展示面板之後要做的事
});
ts.showPanel ();
});
</script>

  這個例子有一個簡單的 TabStrip 類,其中有個 showPanel 方法。這個方法激發兩個事件,onShowPanel 和 afterShowPanel。這個能力是通過用Y.EventTarget 擴大類來實現的。一旦做成,我們就執行個體化了一個 TabStrip 對象,並將一堆處理器都分配給它。這是用來處理執行個體的唯一行為而又能避免混亂當前類的常用代碼。

  總結

  如果你打算重用代碼,無論是在同一網頁,同一網站還是跨項目操作,考慮一下在類裡面將其打包和組織起來。物件導向 JavaScript 很自然地協助實現更好的程式碼群組織以及代碼重用。除此以外,有點遠見的你可以確保代碼具有足夠的靈活性,可以在你寫完代碼後持續使用很長時間。編寫可重用的 不過時 JavaScript 代碼可以節省你,你的團隊還有你公司的時間和金錢。這絕對能讓你大受歡迎。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.