javascript
怎样写出可维护的面向对象javascript(译)
?
原文地址:How to Write Maintainable OO JavaScript Code
利用面向?qū)ο蟮姆椒ň帉慾avascript能幫你省錢,而且也會(huì)讓你的代碼看起來(lái)更酷。不相信?要么你或者別人會(huì)回來(lái)維護(hù)你的代碼。而容易維護(hù)的代碼更容易節(jié)省如金錢般寶貴的時(shí)間。也會(huì)讓你在團(tuán)隊(duì)中更受歡迎,因?yàn)槟銊倓傋屗麄儚念^疼中解脫出來(lái)。在我們寫面向?qū)ο蟠a之前,需要先大致了解一下什么是OO。如果你覺(jué)得你對(duì)OO已經(jīng)足夠了解,可以直接跳過(guò)這段。
什么是OO
面向?qū)ο缶幊炭梢员容^基本地展現(xiàn)你代碼中抽象自現(xiàn)實(shí)物質(zhì)的對(duì)象。在代碼中創(chuàng)建一個(gè)對(duì)象,我們需要先創(chuàng)建一個(gè)類。類幾乎可以表示任何東西:賬戶,雇員,導(dǎo)航菜單,車輛,植物,廣告,飲料,等等。然后,每次你創(chuàng)建一個(gè)對(duì)象,你要從一個(gè)類中繼承下來(lái)。換句話說(shuō),你創(chuàng)建了類的一個(gè)實(shí)例,從而提供了一個(gè)可以操作的對(duì)象。實(shí)際上,使用對(duì)象的最佳時(shí)機(jī)就是當(dāng)你要應(yīng)付多個(gè)食物。否則,一個(gè)簡(jiǎn)單的函數(shù)型程序也會(huì)表現(xiàn)的一樣好。對(duì)象本質(zhì)上是一個(gè)數(shù)據(jù)的容器。比如在雇員的對(duì)象中,你可以存進(jìn)他們的雇工號(hào),名字,起始時(shí)間,頭銜,薪資,特權(quán)等等…對(duì)象也包括了一些函數(shù)(也叫做方法)去操作數(shù)據(jù)。方法也可以用來(lái)充當(dāng)一個(gè)中間人來(lái)確保數(shù)據(jù)完整。這個(gè)中間人通常用來(lái)在存儲(chǔ)前轉(zhuǎn)換數(shù)據(jù)。比如,一個(gè)方法可以接受一個(gè)任意格式的數(shù)據(jù)然后把這個(gè)數(shù)據(jù)轉(zhuǎn)換成標(biāo)準(zhǔn)數(shù)據(jù)并存儲(chǔ)進(jìn)去。最后類可以繼承自其他類。繼承機(jī)制允許你去重用其他類的代碼。比如,銀行賬戶類和影音商店帳號(hào)類可以都繼承自一個(gè)基礎(chǔ)帳號(hào)類,這個(gè)基礎(chǔ)帳號(hào)的類可以提供一些配置信息,帳號(hào)創(chuàng)建時(shí)間,分支信息等等。然后,每一個(gè)繼承出來(lái)的類都可以定義自己的匯報(bào)和租金,控制數(shù)據(jù)結(jié)構(gòu)和方法。
注意:javascript OO是不同于其他語(yǔ)言的OO的
在前一節(jié)我已經(jīng)指出了一個(gè)基于類的面向?qū)ο缶幊痰幕咎攸c(diǎn)。我把它成為基于類的編程,因?yàn)閖avascript并不遵守這些規(guī)則。javascript類是以函數(shù)的形式表現(xiàn),繼承方法是用基于原型的方式實(shí)現(xiàn)。原型繼承和其他類的繼承有很大的區(qū)別,他們是繼承自含有原型屬性的對(duì)象。
對(duì)象的實(shí)例化
這里有一個(gè)實(shí)例化對(duì)象的例子。
// Define the Employee class function Employee(num, fname, lname){this.getFullName = function () {return fname + " " + lname;}};// Instantiate an Employee objectvar john = new Employee("4815162342", "John", "Doe");alert("The employee's full name is " + john.getFullName());有一些地方必須得注意一下:
1.我將類名的首字母大寫了。這個(gè)重要的區(qū)別可以讓人們知道這是一個(gè)用來(lái)實(shí)例化的類,而不是作為一個(gè)普通方法來(lái)調(diào)用的。
2.我使用”new”操作符來(lái)實(shí)例化類。如果在實(shí)例化的時(shí)候忘了寫上new會(huì)導(dǎo)致直接執(zhí)行這個(gè)函數(shù)。
3.getFullName這個(gè)方法是對(duì)外開(kāi)放的,因?yàn)檫@個(gè)方法是注冊(cè)在this對(duì)象上的,而fname和lname是私有的變量,不對(duì)外開(kāi)放。Employee函數(shù)創(chuàng)建的閉包允許getFullName函數(shù)去訪問(wèn)fname和lname,而讓其他方法都無(wú)法直接訪問(wèn)到fname和lname。
原型繼承
這里有一個(gè)原型繼承的例子
// Define Human classfunction Human(){this.setName = function (fname, lname) {this.fname = fname;this.lname = lname;}this.getFullName = function () {return this.fname + " " + this.lname;}}// Define the Employee classfunction Employee(num) {this.getNum = function () {return num;}};// Let Employee inherit from HumanEmployee.prototype = new Human();// Instantiate an Employee objectvar john = new Employee("4815162342");john.setName("John", "Doe");alert(john.getFullName() + "'s employee number is " + john.getNum());我創(chuàng)建了一個(gè)Human類,里面包括了一些人類應(yīng)該有的屬性–我也在Human類里設(shè)置了fname和lname,因?yàn)樗械娜祟?#xff0c;不僅僅是雇員才有名字。然后我讓雇員類的原型指向Human對(duì)象,這樣就擴(kuò)展了雇員類的屬性。
通過(guò)繼承的方式重用代碼
在前一個(gè)例子里,我將原來(lái)的Employee類分割成兩個(gè)。我把所有一個(gè)人類應(yīng)該有的屬性都轉(zhuǎn)移到Human類里,然后讓Employee類去繼承Human類的屬性。這樣的話,Human類里有的屬性可以繼承給其他對(duì)象,比如Student, Client, Citizen, Visitor等等。這是一個(gè)非常棒的方式去重用代碼,這樣我們就不用重復(fù)地給每一個(gè)對(duì)象設(shè)置重復(fù)的屬性。而且如果我們想增加一個(gè)屬性,比如say,或者middle name,那么我們只需要修改一次就可以讓所有的繼承自Human類的類都擁有這個(gè)屬性。相反,如果我們只想增加一個(gè)middle name屬性給一個(gè)對(duì)象,我們可以直接修改這個(gè)對(duì)象,而不用去修改Human
公有和私有屬性
我比較喜歡去提及類里的公有和私有變量。根據(jù)你對(duì)數(shù)據(jù)不同的操作,你會(huì)想把數(shù)據(jù)分為公有數(shù)據(jù)和私有數(shù)據(jù)。一個(gè)私有的屬性并不代表其他人不想訪問(wèn),只是你希望別人能通過(guò)你提供的一個(gè)方法來(lái)操作數(shù)據(jù)。
只讀屬性
有時(shí)候,你只想讓一個(gè)值在對(duì)象被創(chuàng)建的時(shí)候只定義一次。這個(gè)值定義之后,你不想任何人來(lái)修改這個(gè)值。為了做到這個(gè),你可以創(chuàng)建一個(gè)私有變量,然后在實(shí)例化這個(gè)類的時(shí)候設(shè)置它的值
function Animal(type) {var data = [];data['type'] = type;this.getType = function () {return data['type'];}]}var fluffy = new Animal('dog');fluffy.getType(); // returns 'dog'在這個(gè)例子里,我在Animal類里創(chuàng)建了一個(gè)數(shù)組data。當(dāng)一個(gè)Animal對(duì)象被實(shí)例化的時(shí)候,type的值會(huì)傳進(jìn)來(lái),然后設(shè)置data的值。這個(gè)值不能被重寫,因?yàn)樗茿nimal類私有的。讀取type值的唯一一個(gè)方式就是實(shí)例化之后調(diào)用你提供getType方法。因?yàn)間etType是在Animal類內(nèi)部定義的,它可以訪問(wèn)data。這樣,人們就可以讀取這個(gè)對(duì)象的type值,但是不能修改。
但是有一點(diǎn)很重要,這個(gè)讓類內(nèi)部值只讀的方法在另外一個(gè)對(duì)象繼承其之后會(huì)失效。每一個(gè)對(duì)象的實(shí)例都可以共享這些只讀的變量,然后對(duì)其進(jìn)行重寫。最簡(jiǎn)單的解決方法就干脆讓這個(gè)屬性都變成公有屬性算了。如果你一定要讓它們保持私有,你可以用Philippe的方法
公有方法
有些時(shí)候,你希望一些屬性能夠被讀取,也能被修改。那么,你需要通過(guò)這樣的方法來(lái)讓屬性開(kāi)放出來(lái)
function Animal() {this.mood = '';}var fluffy = new Animal();fluffy.mood = 'happy';fluffy.mood; // returns 'happy'現(xiàn)在在我們的Animal類里開(kāi)放了一個(gè)叫做mood的屬性,它可以被讀取也可以被修改。你可以分配一個(gè)函數(shù)去操作這個(gè)屬性。小心不要讓一個(gè)值指向?qū)傩?#xff0c;不然你會(huì)因?yàn)槟阍O(shè)置的值而破壞這個(gè)屬性。
完全私有
最終,你或許也發(fā)現(xiàn)在某個(gè)時(shí)候,你需要一個(gè)完全私有的局部變量。這種情況下,你可以使用像第一個(gè)例子那樣,不過(guò)不需要?jiǎng)?chuàng)建公有方法。
function Animal() {var secret = "You'll never know!"}var fluffy = new Animal();寫出一個(gè)靈活的API
現(xiàn)在我們已經(jīng)把類的創(chuàng)建講完了,我們需要對(duì)其進(jìn)行進(jìn)一步修改,這樣我們就可以跟進(jìn)項(xiàng)目的需求變化。如果你修改了任意一個(gè)項(xiàng)目,或者長(zhǎng)期維護(hù)一個(gè)產(chǎn)品,你會(huì)經(jīng)常遇到項(xiàng)目需求的變化。這是現(xiàn)實(shí)工作中肯定存在的。有時(shí)候,你還在寫代碼的時(shí)候可能就會(huì)因?yàn)樾枨笞兓尨a都作廢了。你可能突然會(huì)需要給標(biāo)簽表單一個(gè)動(dòng)畫,或者通過(guò)Ajax調(diào)用抓取數(shù)據(jù)。雖然不可能預(yù)測(cè)到未來(lái)的變化,但是我們?nèi)匀恍枰θ懗瞿軌虮M量適應(yīng)未來(lái)可能需求的代碼。
Saner參數(shù)列表
有一個(gè)為未來(lái)作準(zhǔn)備的方法就是設(shè)計(jì)參數(shù)列表。這是一個(gè)很重要的方法來(lái)解決那些不確定的需求。你需要避免這樣的參數(shù)列表:
function Person(employeeId, fname, lname, tel, fax, email, email2, dob){ };這樣一個(gè)類是非常脆弱的。如果你在代碼已經(jīng)發(fā)布之后想添加一個(gè)middle name參數(shù)。因?yàn)榇涡虻膯?wèn)題,你不得不在列表的最后來(lái)加入這個(gè)。這樣做顯得非常笨拙。如果你并沒(méi)每一個(gè)參數(shù)的值,那么,傳遞參數(shù)會(huì)很麻煩。比如:
var ara = new Person(1234, "Ara", "Pehlivanian", "514-555-1234", null, null, null, "1976-05-17");一個(gè)更加整潔,更加靈活的方式去傳遞參數(shù)的方式是這樣的:
function Person(employeeId, data) { };第一個(gè)參數(shù)保留,因?yàn)樗潜仨毜摹JO碌亩伎梢哉揭粋€(gè)對(duì)象里,這樣就靈活多了
var ara = new Person(1234, {fname: "Ara",lname: "Pehlivanian",tel: "514-555-1234",dob: "1976-05-17"});這個(gè)方法的優(yōu)異之處在于易于讀取數(shù)據(jù),而且也非常靈活。我們可以注意到fax,email和email2都完全被移除。因?yàn)閷?duì)象不需要特地的順序,所以添加一個(gè)middle name參數(shù)只需要直接把它扔進(jìn)去。
var ara = new Person(1234, {fname: "Ara",mname: "Chris",lname: "Pehlivanian",tel: "514-555-1234",dob: "1976-05-17"});類內(nèi)部的代碼也不需要考慮參數(shù)的順序,因?yàn)槲覀兪沁@樣調(diào)用的:
function Person(employeeId, data) {this.fname = data['fname'];};如果data['fname']返回一個(gè)值,那么它就被賦值了。否則,它就沒(méi)有被賦值。
讓類插件化
隨著時(shí)間的推進(jìn),產(chǎn)品需求或許會(huì)是要在類里添加特定的行為。而這個(gè)行為經(jīng)常和你的類核心方法沒(méi)有關(guān)系。也有可能只有一個(gè)實(shí)施類的需求,比如當(dāng)抓取外部數(shù)據(jù)時(shí),讓tab標(biāo)簽對(duì)應(yīng)的內(nèi)容消失。你也許可以試著把這些功能放進(jìn)你的類,但是他們并不屬于這個(gè)類。一個(gè)tab效果的職責(zé)去管理tab標(biāo)簽。動(dòng)畫效果和數(shù)據(jù)抓取完全是兩個(gè)分離開(kāi)來(lái)的方法。需要在tab之外維護(hù)。唯一一個(gè)方法去讓你的tab適應(yīng)未來(lái)需求,讓臨時(shí)方法調(diào)用自外部,就是允許人們?nèi)ピ谀愕拇a中添加插件。換句話說(shuō),允許人們?nèi)燧d行為, 就像onTabChange, afterTabChange, onShowPanel, afterShowPanel等等。這樣的話,他們就可以很簡(jiǎn)單的掛載你的onShowPanel事件,寫一個(gè)控制方法來(lái)讓一塊內(nèi)容漸漸消失,而且也減輕了大家的壓力。javascript庫(kù)可以讓你非常輕松的完成這件事情,不過(guò)你自己來(lái)完成也不會(huì)太難。這里有一個(gè)基于YUI3的簡(jiǎn)單例子:
<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'); // Code to show the panelthis.fire('afterShowPanel');};};// Give TabStrip the ability to fire custom eventsY.augment(TabStrip, Y.EventTarget);var ts = new TabStrip();// Set up custom event handlers for this instance of TabStripts.on('onShowPanel', function () {//Do something before showing panel});ts.on('onShowPanel', function () {//Do something else before showing panel});ts.on('afterShowPanel', function () {//Do something after showing panel});ts.showPanel();});</script>這個(gè)例子有一個(gè)簡(jiǎn)單的含有showPanel方法的TabStrip類。這個(gè)方法會(huì)綁定了兩個(gè)事件,onShowPanel和afterShowPanel。將Y.EventTarget合并到你的類中就可以實(shí)現(xiàn)這樣的綁定。完成之后,我們實(shí)例一個(gè)TabStrip對(duì)象,然后分配一個(gè)對(duì)應(yīng)事件控制方法。這就是一個(gè)典型的代碼,用于控制實(shí)例中獨(dú)特的行為,也不會(huì)污染目前的類。
總結(jié)
如果你有計(jì)劃在同一頁(yè)面,同一站點(diǎn)或者多個(gè)項(xiàng)目中重用代碼,想要通過(guò)類的形式將其整理和組織。面向?qū)ο蟮膉avascript非常自然地提供了更加好的組織和重用能力。只要你稍微深謀遠(yuǎn)慮一下,你就可以確定你的代碼在長(zhǎng)期內(nèi)都是足夠靈活的。寫出可重用,適應(yīng)未來(lái)的javascript會(huì)節(jié)約你,你的團(tuán)隊(duì)和你的公司的事件和金錢。它當(dāng)然也能讓你變的更酷
總結(jié)
以上是生活随笔為你收集整理的怎样写出可维护的面向对象javascript(译)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Ontheinternet的深圳论坛
- 下一篇: 分别标记区段的 飞鸽传书 左、右端