偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

前端優(yōu)秀實(shí)踐之可維護(hù)性

開(kāi)發(fā) 前端
編寫可維護(hù)的代碼十分重要,因?yàn)楹芏嚅_(kāi)發(fā)者都會(huì)花大量時(shí)間去維護(hù)別人寫的代碼。讓自己的代碼容易維護(hù),可以保證其他開(kāi)發(fā)者更好地完成自己的工作。

在早期網(wǎng)站中,JavaScript主要用于實(shí)現(xiàn)一些小型動(dòng)效或表單驗(yàn)證。今天的Web應(yīng)用則動(dòng)輒成千上萬(wàn)行JavaScript代碼,用于完成各種各樣復(fù)雜的處理。這些變化要求開(kāi)發(fā)者把可維護(hù)能力放到重要位置上。正如更傳統(tǒng)意義上的軟件工程師一樣,JavaScript工程師受雇是要為公司創(chuàng)造價(jià)值的?,F(xiàn)代前端工程師的使命,不僅僅是要保證產(chǎn)品如期上線,更重要的是要隨著時(shí)間推移為公司不斷積累知識(shí)資產(chǎn)。

[[324237]]

編寫可維護(hù)的代碼十分重要,因?yàn)楹芏嚅_(kāi)發(fā)者都會(huì)花大量時(shí)間去維護(hù)別人寫的代碼。實(shí)際開(kāi)發(fā)中,從第一行代碼開(kāi)始寫起的情況是非常少見(jiàn)的。通常都是要在別人的代碼之上來(lái)構(gòu)建自己的工作。讓自己的代碼容易維護(hù),可以保證其他開(kāi)發(fā)者更好地完成自己的工作。

注意:可維護(hù)代碼的概念并不止適用于JavaScript,其中很多概念適用于任何編程語(yǔ)言。當(dāng)然,有部分概念可能確實(shí)是特定于JavaScript的。

1. 什么是可維護(hù)的代碼

可維護(hù)的代碼有幾個(gè)特點(diǎn)。通常,說(shuō)代碼可維護(hù)就意味著它具備如下特點(diǎn)。

  • 容易理解:無(wú)需求助原始開(kāi)發(fā)者,任何人一看代碼就知道是干什么的,怎么實(shí)現(xiàn)的。
  • 符合常識(shí):代碼中的一切都顯得自然而然,無(wú)論操作有多么復(fù)雜。
  • 容易適配:即使數(shù)據(jù)發(fā)生變化也不用完全重寫。
  • 容易擴(kuò)展:代碼架構(gòu)經(jīng)過(guò)認(rèn)真設(shè)計(jì),支持未來(lái)擴(kuò)展核心功能。
  • 容易調(diào)試:出問(wèn)題時(shí),代碼可以給出明確的信息,通過(guò)它能直接定位問(wèn)題。

能夠?qū)懗隹删S護(hù)的JavaScript代碼是一項(xiàng)重要的專業(yè)技能。這個(gè)技能是一個(gè)周末就拼湊一個(gè)網(wǎng)站的業(yè)余愛(ài)好者和對(duì)自己所做的一切都深思熟慮的專業(yè)開(kāi)發(fā)者的重要區(qū)別。

2. 編碼規(guī)范

編寫可維護(hù)代碼的第一步是認(rèn)真考慮編碼規(guī)范。編碼規(guī)范在多數(shù)編程語(yǔ)言中都會(huì)涉及,簡(jiǎn)單上網(wǎng)一搜,就可以找到成千上萬(wàn)的相關(guān)文章。專業(yè)組織都會(huì)有為開(kāi)發(fā)者建立的編碼規(guī)范,旨在讓人寫出更容易維護(hù)的代碼。優(yōu)秀開(kāi)源項(xiàng)目都有嚴(yán)格的編碼規(guī)范,能夠讓社區(qū)的所有人容易理解代碼的組織。

編碼規(guī)范對(duì)JavaScript而言非常重要,因?yàn)檫@門語(yǔ)言實(shí)在太靈活了。與多數(shù)面向?qū)ο笳Z(yǔ)言不同,JavaScript并不強(qiáng)迫開(kāi)發(fā)者把任何東西都定義為對(duì)象。相反,JavaScript支持任何編程風(fēng)格,包括傳統(tǒng)的面向?qū)ο缶幊毯吐暶魇骄幊?,以及函?shù)式編程。簡(jiǎn)單看幾個(gè)開(kāi)源的JavaScript庫(kù),就會(huì)發(fā)現(xiàn)有很多方式可以創(chuàng)建對(duì)象、定義方法和管理環(huán)境。

接下來(lái)的幾節(jié)會(huì)討論制定編碼規(guī)范的一些基礎(chǔ)方面。這些主題都很重要,當(dāng)然每個(gè)人的需求不同,實(shí)現(xiàn)方式也可以不同。

2.1 可讀性

要想讓代碼容易維護(hù),首先必須讓人容易看懂??勺x性必須考慮代碼是一種文本文件。為此,代碼縮進(jìn)是保證可讀性的重要基礎(chǔ)。如果所有人都使用相同的縮進(jìn),整個(gè)項(xiàng)目的代碼就會(huì)更容易看懂??s進(jìn)通常要使用空格數(shù)而不是Tab(制表符)來(lái)定義,因?yàn)楹笳咴诓煌谋揪庉嬈髦械娘@示會(huì)有差異。一般來(lái)說(shuō),縮進(jìn)都是以4個(gè)空格為單位,當(dāng)然具體多少個(gè)可以自己定。

可讀性的另一方面是代碼注釋。在多數(shù)編程語(yǔ)言中,廣泛接受的做法是為每個(gè)方法都編寫注釋。由于JavaScript可以在代碼中任何地方創(chuàng)建函數(shù),所以這一點(diǎn)容易被忽視。正因?yàn)槿绱耍赡芙oJavaScript中的每個(gè)函數(shù)都寫注釋才更重要。一般來(lái)說(shuō),以下這些地方都是應(yīng)該寫注釋的。

  • 函數(shù)和方法。每個(gè)函數(shù)和方法都應(yīng)該有注釋來(lái)描述其用途,以及完成任務(wù)所用的算法。同時(shí),也寫清使用這個(gè)函數(shù)或方法的前提(假設(shè))、每個(gè)參數(shù)的含義,以及函數(shù)是否返回值(因?yàn)橥ㄟ^(guò)函數(shù)定義看不出來(lái))。
  • 大型代碼塊。多行代碼但用于完成單一任務(wù)的,應(yīng)該在前面給出注釋,把要完成的任務(wù)寫清楚。
  • 復(fù)雜的算法。如果使用了不同尋常的手法解決了問(wèn)題,要通過(guò)注釋解釋明白。這樣不僅可以幫到別人,也可以讓自己今后再看的時(shí)候更快想起來(lái)。
  • 使用黑科技。由于瀏覽器之間的差異,JavaScript代碼中通常都會(huì)包含一些黑科技。不要假設(shè)其他人一看就能明白某個(gè)黑科技是為了解決某個(gè)瀏覽器的什么問(wèn)題。如果對(duì)某個(gè)瀏覽器不能使用正常方式達(dá)到目的,那要在注釋里把黑科技的用途寫出來(lái)。這樣可以避免別人誤以為黑科技沒(méi)有用而把它“修復(fù)”掉,結(jié)果你已經(jīng)修好的問(wèn)題又會(huì)復(fù)現(xiàn)。

縮進(jìn)和注釋可以讓代碼更容易理解,將來(lái)也更容易維護(hù)。

2.2 變量和函數(shù)命名

變量和函數(shù)的適當(dāng)命名對(duì)于可讀性和可維護(hù)性也是至關(guān)重要的。由于很多JavaScript開(kāi)發(fā)者都是“草莽”出身,所以很容易用foo、bar命名變量,用doSomething來(lái)命名函數(shù)。專業(yè)JavaScript開(kāi)發(fā)者必須改掉這些積習(xí),這樣才能寫出可維護(hù)的代碼。以下是關(guān)于命名的通用規(guī)則。

  • 變量名應(yīng)該是名詞,例如car或person。
  • 函數(shù)名應(yīng)該以動(dòng)詞開(kāi)始,例如getName()。返回布爾值的函數(shù)通常以is開(kāi)頭,比如isEnabled()。
  • 對(duì)變量和函數(shù)都使用符合邏輯的名字,不用擔(dān)心長(zhǎng)度。長(zhǎng)名字的問(wèn)題可以通過(guò)后處理和壓縮解決。
  • 變量、函數(shù)和方法應(yīng)該以小寫字母開(kāi)頭,使用駝峰大小寫形式,如getName()和isPerson。類名應(yīng)該首字母大寫,比如Person、RequestFactory。常量值應(yīng)該全部大寫并以下劃線相接,比如REQUEST_TIMEOUT。
  • 名字要盡量用描述性和直觀的詞匯,但不要過(guò)于冗長(zhǎng)。getName()一看就知道會(huì)返回名字,而PersonFactory一看就知道會(huì)產(chǎn)生某個(gè)Person對(duì)象或?qū)嵗?/li>

要完全避免沒(méi)有用的變量名,比如不能表示所包含數(shù)據(jù)的類型。通過(guò)適當(dāng)命名,代碼讀起來(lái)就會(huì)像故事,因此更容易理解。

2.3 變量類型透明化

因?yàn)镴avaScript是松散類型的語(yǔ)言,所以很容易忘記變量包含的數(shù)據(jù)類型。適當(dāng)命名可以在某種程度上解決這個(gè)問(wèn)題,但還不夠。有三種方式可以表明變量的數(shù)據(jù)類型。

第一種方式是通過(guò)初始化。定義變量時(shí),應(yīng)該立即將其初始化為一個(gè)將來(lái)要使用類型的值。例如,要保存布爾值的變量可以將其初始化為true或false,而要保存數(shù)值的變量可以將其初始化為一個(gè)數(shù)值。再看幾個(gè)例子:

  1. // 通過(guò)初始化表明變量類型 
  2. let found = false; // Boolean 
  3. let count = -1; // number 
  4. let name = ""; // string 
  5. let person = null; // object 

初始化為特定數(shù)據(jù)類型的值可以明確表示變量的類型。在ES6之前,初始化方式不適合函數(shù)聲明中函數(shù)的參數(shù)。ES6之后,可以在函數(shù)聲明中為參數(shù)指定默認(rèn)值來(lái)表明參數(shù)類型。

第二種表示變量類型的方式是使用匈牙利表示法。匈牙利表示法指的是在變量名前面前綴一個(gè)或多個(gè)字符表示數(shù)據(jù)類型。這種表示法曾經(jīng)在腳本語(yǔ)言中非常流行,很長(zhǎng)時(shí)間以來(lái)也是JavaScript首選的格式。對(duì)于基本數(shù)據(jù)類型,用o表示對(duì)象(object)、s表示字符串(string),i表示整數(shù)(integer),f表示浮點(diǎn)數(shù)(float)、b表示布爾值(boolean)。下面看幾個(gè)例子。

  1. // 使用匈牙利表示法標(biāo)明數(shù)據(jù)類型 
  2. let bFound; // Boolean 
  3. let iCount; // integer 
  4. let sName; // string 
  5. let oPerson; // object 

匈牙利表示法也可以應(yīng)用給函數(shù)參數(shù)。匈牙利表示法的缺點(diǎn)是讓代碼可讀性有所下降,不夠直觀,破壞了類似句子的自然閱讀流暢性。為此,匈牙利表示法已經(jīng)被很多開(kāi)發(fā)者拋棄。

最后一種表明數(shù)據(jù)類型的方式是使用類型注釋。類型注釋放在變量名后面,初始化表達(dá)式的前面?;舅悸肥窃谧兞颗赃吺褂米⑨屨f(shuō)明類型,比如:

  1. // 使用類型注釋標(biāo)明數(shù)據(jù)類型 
  2. let found /*:Boolean*/ = false; 
  3. let count /*:int*/ = 10; 
  4. let name /*:String*/ = "Nicholas"; 
  5. let person /*:Object*/ = null; 

類型注釋在保持整體可讀性的同時(shí)向代碼中注入了類型信息。類型注釋的缺點(diǎn)是不能再使用多行注釋把大型代碼塊注釋掉了。因?yàn)轭愋妥⑨屢彩嵌嘈凶⑨?,因此?huì)造成干擾,如下面的例子所示:

  1. // 這樣多行注釋不會(huì)生效 
  2. /* 
  3. let found /*:Boolean*/ = false; 
  4. let count /*:int*/ = 10; 
  5. let name /*:String*/ = "Nicholas"; 
  6. let person /*:Object*/ = null; 
  7. */ 

這里本來(lái)是想使用多行注釋把所有變量聲明都注釋掉。但類型注釋產(chǎn)生了干擾,因?yàn)榈谝粋€(gè)/*(第2行)會(huì)與第一個(gè)*/(第3行)匹配,結(jié)果會(huì)導(dǎo)致語(yǔ)法錯(cuò)誤。如果想注釋掉包含類型注釋的代碼,只能使用單行注釋一行一行地注釋掉每一行(有的編輯器可以自動(dòng)完成)。

以上是三種標(biāo)明變量數(shù)據(jù)類型的最常用方式。每種方式都有優(yōu)點(diǎn)和缺點(diǎn),可以根據(jù)自己的情況選用。關(guān)鍵要看哪一種最適合自己的項(xiàng)目,并保證一致性。

3. 松散耦合

只要應(yīng)用的某個(gè)部分對(duì)另一個(gè)部分依賴得過(guò)于緊密,代碼就會(huì)變成強(qiáng)耦合,因而難以維護(hù)。典型的問(wèn)題是在一個(gè)對(duì)象中直接引用另一個(gè)對(duì)象。這樣,修改其中一個(gè),可能必須還得修改另一個(gè)。緊密耦合的軟件難于維護(hù),肯定需要頻繁地重寫。

考慮到相關(guān)的技術(shù),Web應(yīng)用在某些情況下可能變得過(guò)于強(qiáng)耦合。關(guān)鍵在于有這個(gè)意識(shí),隨時(shí)注意不要讓代碼產(chǎn)生強(qiáng)耦合。

3.1 解耦HTML/JavaScript

Web開(kāi)發(fā)中最常見(jiàn)的耦合是HTML/JavaScript耦合。在網(wǎng)頁(yè)中,HTML和JavaScript分別代表不同層面的解決方案。HTML是數(shù)據(jù),JavaScript是行為。因?yàn)樗鼈冎g要交互操作,需要通過(guò)不同的方式將這兩種技術(shù)聯(lián)系起來(lái)??上У氖牵渲幸恍┓绞綍?huì)導(dǎo)致HTML與JavaScript強(qiáng)耦合。

把JavaScript直接嵌入在HTML中,包括使用<script>元素包含嵌入代碼或使用HTML屬性添加事件處理程序,都會(huì)造成強(qiáng)耦合。比如下面的例子:

  1. <!-- 使用<script>造成HTML/JavaScript強(qiáng)耦合 --> 
  2. <script> 
  3.     document.write("Hello world!"); 
  4. </script> 
  5.  
  6. <!-- 使用事件處理程序?qū)傩栽斐蒆TML/JavaScript強(qiáng)耦合 --> 
  7. <input type="button" value="Click Me" onclick="doSomething()"/> 

雖然技術(shù)上這樣做沒(méi)有問(wèn)題,但實(shí)踐中這樣會(huì)導(dǎo)致HTML的數(shù)據(jù)與JavaScript的行為緊密耦合在一起。理想情況下,HTML和JavaScript應(yīng)該完全分開(kāi),通過(guò)外部文件引入JavaScript,然后使用DOM添加行為。

HTML與JavaScript強(qiáng)耦合的情況下,每次分析JavaScript的報(bào)錯(cuò)都要首先確定錯(cuò)誤來(lái)自HTML還是JavaScript。而且,這樣也會(huì)引入代碼可用性的新錯(cuò)誤。在這個(gè)例子中,用戶可能會(huì)在doSomething()函數(shù)可用之前點(diǎn)擊按鈕,從而導(dǎo)致JavaScript報(bào)錯(cuò)。由于每次修改按鈕的行為都需要既改HTML又改JavaScript,而實(shí)際上只有后者才是有必要修改的。這樣就會(huì)降低代碼的可維護(hù)性。

在相反的情況下,HTML和JavaScript也會(huì)變得強(qiáng)耦合:把HTML包含在JavaScript中。這種情況通常發(fā)生在把一段HTML通過(guò)innerHTML插入到頁(yè)面中,比如:

  1. // HTML緊耦合到了JavaScript 
  2. function insertMessage(msg) { 
  3.   let container = document.getElementById("container"); 
  4.     container.innerHTML = `<div class="msg"> 
  5.       <p> class="post">${msg}</p> 
  6.       <p><em>Latest message above.</em></p> 
  7.     </div>`; 

一般來(lái)說(shuō),應(yīng)該避免在JavaScript中創(chuàng)建大量HTML。同樣,這主要是為了做到數(shù)據(jù)層和行為層各司其職,在出錯(cuò)時(shí)更容易定位問(wèn)題所在。如果使用上面的代碼示例,那么如果動(dòng)態(tài)插入的HTML格式不對(duì),就會(huì)造成頁(yè)面布局出錯(cuò)。但在這種情況下定位錯(cuò)誤就更困難了。因?yàn)檫@時(shí)候通常首先會(huì)去找頁(yè)面中出錯(cuò)的HTML源代碼,但又找不到,因?yàn)樗莿?dòng)態(tài)生成的。而且修改數(shù)據(jù)或頁(yè)面,還需要修改JavaScript,這說(shuō)明兩層是緊密耦合的。

HTML渲染應(yīng)該盡可能與JavaScript分開(kāi)。在使用JavaScript插入數(shù)據(jù)時(shí),應(yīng)該盡可能不要插入標(biāo)記。相應(yīng)的標(biāo)記可以包含并隱藏在頁(yè)面中,在需要的時(shí)候JavaScript可以直接用它來(lái)顯示,而不需要?jiǎng)討B(tài)生成。另一個(gè)辦法是通過(guò)Ajax請(qǐng)求獲取要顯示的HTML,這樣也可以保證同一個(gè)渲染層(PHP、JSP、Ruby等)負(fù)責(zé)輸出標(biāo)記,而不是把標(biāo)記嵌在JavaScript中。

解耦HTML和JavaScript可以節(jié)省排錯(cuò)時(shí)間,因?yàn)楦菀锥ㄎ诲e(cuò)誤來(lái)源。同樣解耦也有助于保證可維護(hù)性,對(duì)行為的修改只涉及JavaScript,而對(duì)標(biāo)記的修改則只涉及要渲染的文件。

3.2 解耦CSS/JavaScript

Web應(yīng)用的另一層是CSS,主要負(fù)責(zé)頁(yè)面的外觀。JavaScript和CSS是緊密相關(guān)的,它們都是建構(gòu)在HTML之上的,因此也經(jīng)常一起使用。與HTML和JavaScript的情況類似,CSS也可能與JavaScript產(chǎn)生強(qiáng)耦合。最常見(jiàn)的例子就是使用JavaScript修改個(gè)別樣式,比如:

  1. // CSS緊耦合到了JavaScript 
  2. element.style.color = "red"
  3. element.style.backgroundColor = "blue"

因?yàn)镃SS負(fù)責(zé)頁(yè)面外觀,任何樣式的問(wèn)題都應(yīng)該通過(guò)CSS文件解決。可是,如果JavaScript直接修改個(gè)別樣式(比如顏色),就會(huì)增加一個(gè)排錯(cuò)時(shí)要考慮甚至要修改的因素。結(jié)果是JavaScript某種程度上承擔(dān)了頁(yè)面顯示的任務(wù),與CSS搞成了緊密耦合。如果將來(lái)有一天要修改樣式,那么CSS和JavaScript都需要修改。這對(duì)負(fù)責(zé)維護(hù)的開(kāi)發(fā)者來(lái)說(shuō)是一個(gè)惡夢(mèng)。層與層的清晰解耦是必需的。

現(xiàn)代Web應(yīng)用經(jīng)常使用JavaScript改變樣式,因此雖然不太可能完全解耦CSS和JavaScript,但可以讓這種耦合變成更松散。這主要可以通過(guò)動(dòng)態(tài)修改類名而不是樣式來(lái)實(shí)現(xiàn),比如:

  1. // CSS與JavaScript松散耦合 
  2. element.className = "edit"

通過(guò)修改元素的CSS類名,可以把大部分樣式限制在CSS文件里。JavaScript只負(fù)責(zé)修改應(yīng)用樣式的類名,而不直接影響元素的樣式。只要應(yīng)用的類名沒(méi)錯(cuò),那么外觀的問(wèn)題就只跟CSS有關(guān),而跟JavaScript無(wú)關(guān)。

同樣,保證層與層之間的適當(dāng)分離是至關(guān)重要的。顯示出問(wèn)題就只應(yīng)該去CSS里解決,行為出問(wèn)題就只應(yīng)該找JavaScript的問(wèn)題。這些層之間的松散耦合可以提升整個(gè)應(yīng)用的可維護(hù)性。

3.3 解耦應(yīng)用邏輯/事件處理程序

每個(gè)Web應(yīng)用中都會(huì)有大量事件處理程序在監(jiān)聽(tīng)各種事件??墒?,其中很少有真正做到應(yīng)用邏輯與事件處理程序分離的。來(lái)看下面的例子:

  1. function handleKeyPress(event) { 
  2.   if (event.keyCode == 13) { 
  3.     let target = event.target; 
  4.     let value = 5 * parseInt(target.value); 
  5.     if (value > 10) { 
  6.       document.getElementById("error-msg").style.display = "block"
  7.     } 
  8.   } 

這個(gè)事件處理程序除了處理事件,還包含了應(yīng)用邏輯。這樣做的問(wèn)題是雙重的。首先,除了事件沒(méi)有辦法觸發(fā)應(yīng)用邏輯,結(jié)果造成調(diào)試?yán)щy。如果沒(méi)有發(fā)生預(yù)期的結(jié)果怎么辦?是因?yàn)闆](méi)有調(diào)用事件處理程序,還是因?yàn)閼?yīng)用邏輯有錯(cuò)誤?其次,如果后續(xù)事件也會(huì)對(duì)應(yīng)相同的應(yīng)用邏輯,就會(huì)導(dǎo)致代碼重復(fù),否則就要把它提取到一個(gè)函數(shù)中。無(wú)論如何,都會(huì)導(dǎo)致原本不必要的多余工作。

更好的做法是將應(yīng)用邏輯與事件處理程序分開(kāi),各自只負(fù)責(zé)處理各自的事情。事件處理程序應(yīng)該專注于event對(duì)象的相關(guān)信息,然后把這些信息傳給處理應(yīng)用邏輯的某些方法。例如,前面的例子可以重寫成這樣:

  1. function validateValue(value) { 
  2.   value = 5 * parseInt(value); 
  3.   if (value > 10) { 
  4.     document.getElementById("error-msg").style.display = "block"
  5.   } 
  6.  
  7. function handleKeyPress(event) { 
  8.   if (event.keyCode == 13) { 
  9.     let target = event.target; 
  10.     validateValue(target.value); 
  11.   } 

這樣修改之后,應(yīng)用邏輯跟事件處理程序就分開(kāi)了。handleKeyPress()函數(shù)只負(fù)責(zé)檢查用戶是不是按下了回車鍵(event.keyCode等于13),如果是則取得事件目標(biāo),并把目標(biāo)的值傳給validateValue()函數(shù),由該函數(shù)處理應(yīng)用邏輯。注意,validateValue()函數(shù)中不包含任何依賴事件處理程序的代碼。這個(gè)函數(shù)只負(fù)責(zé)接收一個(gè)值,然后可以對(duì)這個(gè)值做任何處理。

把應(yīng)用邏輯從事件處理程序中分離出來(lái)有很多好處。首先,可以方便地修改觸發(fā)某個(gè)流程的事件。如果原來(lái)是通過(guò)鼠標(biāo)單擊觸發(fā)流程,而現(xiàn)在又想增加鍵盤操作來(lái)觸發(fā),那么修改起來(lái)也很簡(jiǎn)單。其次,可以在不用添加事件的情況下測(cè)試代碼,這樣創(chuàng)建單元測(cè)試甚至與應(yīng)用自動(dòng)化整合都會(huì)更簡(jiǎn)單。

以下是在解耦應(yīng)用和業(yè)務(wù)邏輯時(shí)應(yīng)該注意的幾點(diǎn)。

  • 不要把event對(duì)象傳給其他方法,而是只傳遞event對(duì)象中必要的數(shù)據(jù)。
  • 應(yīng)用中每個(gè)可能的操作都應(yīng)該無(wú)需事件處理程序就可以執(zhí)行。
  • 事件處理程序應(yīng)該處理事件,而把后續(xù)處理交給應(yīng)用邏輯。

做到上述幾點(diǎn)能夠給任何代碼的可維護(hù)性帶來(lái)巨大的提升,同時(shí)也能為將來(lái)的測(cè)試和開(kāi)發(fā)提供很多可能性。

4. 編碼慣例

編寫可維護(hù)的JavaScript不僅僅涉及代碼格式和規(guī)范,也涉及代碼做什么。大企業(yè)開(kāi)發(fā)Web應(yīng)用通常需要很多人協(xié)同工作。這時(shí)候就需要保證每個(gè)人的瀏覽器環(huán)境都有恒定不變的規(guī)則。為此,開(kāi)發(fā)者應(yīng)該遵守某些編碼慣例。

4.1 尊重對(duì)象所有權(quán)

JavaScript的動(dòng)態(tài)天性意味著幾乎可以在任何時(shí)候修改任何東西。過(guò)去有人說(shuō),JavaScript中沒(méi)有什么是神圣不可侵犯的,因?yàn)椴荒馨讶魏螙|西標(biāo)記為最終結(jié)果或者恒定不變。但ECMAScript 5引入防篡改對(duì)象之后,情況不同了。當(dāng)然,對(duì)象默認(rèn)還是可以修改的。在其他語(yǔ)言中,在沒(méi)有源代碼的情況下對(duì)象和類都是不可修改的。JavaScript則允許在任何時(shí)候修改任何對(duì)象,因此就可能導(dǎo)致意外地覆蓋默認(rèn)行為。既然這門語(yǔ)言沒(méi)有什么限制,那就需要開(kāi)發(fā)者自己限制自己。

在企業(yè)開(kāi)發(fā)中,可能最最重的編碼慣例就是尊重對(duì)象所有權(quán),這意味著不要修改不屬于你的對(duì)象。簡(jiǎn)單地講,如果你不負(fù)責(zé)創(chuàng)建和維護(hù)某個(gè)對(duì)象,包括它的構(gòu)造函數(shù)或它的方法,就不應(yīng)該對(duì)它進(jìn)行任何修改。更具體一點(diǎn)說(shuō),就是:

  • 不要給實(shí)例或原型添加屬性
  • 不要給實(shí)例或原型添加方法
  • 不要重定義已有的方法

問(wèn)題在于,開(kāi)發(fā)者會(huì)假設(shè)瀏覽器環(huán)境以某種方式運(yùn)行。修改了多個(gè)人使用的對(duì)象也就意味著會(huì)有錯(cuò)誤發(fā)生。如果有人希望某個(gè)函數(shù)叫stopEvent(),用于取消某個(gè)事件的默認(rèn)行為。然后,你把它給改了,除了取消事件的默認(rèn)行為,又添加了其他事件處理程序??上攵?,問(wèn)題肯定會(huì)接踵而至。別人還會(huì)認(rèn)為這個(gè)函數(shù)只做最開(kāi)始的那點(diǎn)事,由于對(duì)它后來(lái)添加的副作用并不知情,很可能會(huì)用錯(cuò)或者造成損失。

以上規(guī)則不僅適用于自定義類型和對(duì)象,同樣適用于原生類型和對(duì)象,比如Object、String、document、window,等等??紤]到瀏覽器廠商也有可能會(huì)在不宣布的情況下以非預(yù)期方式修改這些對(duì)象,那么潛在的風(fēng)險(xiǎn)就更大了。

以前有一個(gè)流行的Prototype庫(kù)就發(fā)生過(guò)類似事件。當(dāng)時(shí),這個(gè)庫(kù)在document對(duì)象上實(shí)現(xiàn)了getElementsByClassName()方法,返回一個(gè)Array的實(shí)例,而這個(gè)實(shí)例上還增加了each()方法。jQuery的作者John Resig后來(lái)在自己的博客上分析了這個(gè)問(wèn)題造成的影響。他在博客中(https://johnresig.com/blog/getelementsbyclassname-pre-prototype-16/)指出這個(gè)問(wèn)題是由于瀏覽器也原生實(shí)現(xiàn)了相同的getElementsByClassName()方法造成的。但Prototype的同名方法返回的是Array而非NodeList,后者沒(méi)有each()方法。使用這個(gè)庫(kù)的開(kāi)發(fā)者之前會(huì)寫這樣的代碼:

  1. document.getElementsByClassName("selected").each(Element.hide); 

盡管這樣寫在沒(méi)有原生實(shí)現(xiàn)getElementsByClassName()方法的瀏覽器里沒(méi)有問(wèn)題,但在實(shí)現(xiàn)它的瀏覽器里就會(huì)出問(wèn)題。因?yàn)閮蓚€(gè)同名方法返回的結(jié)果不一樣。我們不能預(yù)見(jiàn)瀏覽器廠商將來(lái)會(huì)怎么修改原生對(duì)象,因此不管怎么修改它們都可能在將來(lái)某個(gè)時(shí)刻出現(xiàn)沖突時(shí)導(dǎo)致問(wèn)題。

為此,最好的方法是永遠(yuǎn)不要修改不屬于你的對(duì)象。只有你自己創(chuàng)建的才是你的對(duì)象,包括自定義類型和對(duì)象字面量。Array、document等這些都不是你的,因?yàn)樵谀愕拇a執(zhí)行之前它們已經(jīng)存在了??梢赃@樣為對(duì)象添加新功能:

  • 創(chuàng)建包含想要功能的新對(duì)象,通過(guò)它與別人的對(duì)象交互。
  • 創(chuàng)建新自定義類型繼承本來(lái)想要修改的類型,給自定義類型添加新功能。

很多JavaScript庫(kù)目前都贊同這個(gè)開(kāi)發(fā)理念,這樣無(wú)論瀏覽器怎樣改變都可以發(fā)展和適應(yīng)。

4.2 不聲明全局變量

與尊重對(duì)象所有權(quán)密切相關(guān)的是盡可能不聲明全局變量和函數(shù)。同樣,這也關(guān)系到創(chuàng)建一致和可維護(hù)的腳本運(yùn)行環(huán)境。最多可以創(chuàng)建一個(gè)全局變量,作為其他對(duì)象和函數(shù)的命名空間。來(lái)看下面的例子:

  1. // 兩個(gè)全局變量——不要! 
  2. var name = "Nicholas"
  3. function sayName() { 
  4.   console.log(name); 

以上代碼聲明了兩個(gè)全局變量:name和sayName()??梢韵裣旅孢@樣把它們包含在一個(gè)對(duì)象中:

  1. // 一個(gè)全局變量——推薦 
  2. var MyApplication = { 
  3.   name: "Nicholas", 
  4.   sayName: function() { 
  5.     console.log(this.name); 
  6.   } 
  7. }; 

這個(gè)重寫后的版本只聲明了一個(gè)全局對(duì)象MyApplication。在這個(gè)對(duì)象內(nèi)部,又包含name和sayName()。這樣可以避免之前版本的幾個(gè)問(wèn)題。首先,變量name會(huì)覆蓋window.name屬性,而這可能會(huì)影響其他功能。其次,有助于分清功能都集中在哪里。調(diào)用MyApplication.sayName()從邏輯上就會(huì)暗示出現(xiàn)任何問(wèn)題,都可以在MyApplication的代碼中找原因。

這樣一個(gè)全局對(duì)象可以擴(kuò)展為命名空間的概念。命名空間涉及創(chuàng)建一個(gè)對(duì)象,然后通過(guò)這個(gè)對(duì)象來(lái)暴露能力。比如,Google Closure庫(kù)就利用了這樣的命名空間來(lái)組織其代碼。下面是幾個(gè)例子:

  • goog.string:用于操作字符串的方法。
  • goog.html.utils:與HTML相關(guān)的方法。
  • goog.i18n:與國(guó)際化(i18n)相關(guān)的方法。

對(duì)象goog就相當(dāng)于一個(gè)容器,其他對(duì)象都包含在這里面。只要使用對(duì)象以這種方式來(lái)組織功能,就可以稱該對(duì)象為命名空間。整個(gè)Google Closure庫(kù)都構(gòu)建在這個(gè)概念之上,能夠在同一個(gè)頁(yè)面上與其他JavaScript庫(kù)共存。

關(guān)于命名空間,最重要的是確定一個(gè)所有人都同意的全局對(duì)象名稱。這個(gè)名稱要足夠獨(dú)特,不可能與其他人的沖突。多數(shù)情況下,可以使用開(kāi)發(fā)者所在的公司名,例如goog或Wrox。下面的例子演示了使用Wrox作為命名空間來(lái)組織功能:

  1. // 創(chuàng)建全局對(duì)象 
  2. var Wrox = {}; 
  3.  
  4. // 為本書(Professional JavaScript)創(chuàng)建命名空間 
  5. Wrox.ProJS = {}; 
  6.  
  7. // 添加本書用到的其他對(duì)象 
  8. Wrox.ProJS.EventUtil = { ... }; 
  9. Wrox.ProJS.CookieUtil = { ... }; 

在這個(gè)例子中,Wrox是全局變量,然后在它的下面又創(chuàng)建了命名空間。如果本書所有代碼都保存在Wrox.ProJS命名空間中,那么其他作者的代碼就可以使用自己的對(duì)象來(lái)保存。只要每個(gè)人都遵循這個(gè)模式,就不必?fù)?dān)心有人會(huì)覆蓋這里的EventUtil或CookieUtil,因?yàn)榧词怪孛鼈円仓粫?huì)出現(xiàn)在不同的命名空間中。比如下面的例子:

  1. // 為另一本書(Professional Ajax)創(chuàng)建命名空間 
  2. Wrox.ProAjax = {}; 
  3.  
  4. // 添加其他對(duì)象 
  5. Wrox.ProAjax.EventUtil = { ... }; 
  6. Wrox.ProAjax.CookieUtil = { ... }; 
  7.  
  8. // 可以照常使用ProJS下面的對(duì)象 
  9. Wrox.ProJS.EventUtil.addHandler( ... ); 
  10.  
  11. // 以及ProAjax下面的對(duì)象 
  12. Wrox.ProAjax.EventUtil.addHandler( ... ); 

雖然命名空間需要多寫一點(diǎn)代碼,但從可維護(hù)性角度看,這個(gè)代價(jià)還是非常值得的。命名空間可以確保代碼與頁(yè)面上的其他代碼互不干擾。

4.3 不要比較null

JavaScript不會(huì)自動(dòng)做任何類型檢查,因此就需要開(kāi)發(fā)者擔(dān)起這個(gè)責(zé)任。結(jié)果,很多JavaScript代碼都不會(huì)做類型檢查。最常見(jiàn)的類型檢查是看一個(gè)值是不是null。然而,與null進(jìn)行比較的代碼太多了,其中很多都因?yàn)轭愋蜋z查不夠而頻繁引發(fā)錯(cuò)誤。比如下面的例子:

  1. function sortArray(values) { 
  2.   if (values != null) { // 不要這樣比較! 
  3.     values.sort(comparator); 
  4.   } 

這個(gè)函數(shù)的目的是使用給定的比較函數(shù)對(duì)數(shù)組進(jìn)行排序。為保證函數(shù)正常執(zhí)行,values參數(shù)必須是數(shù)組。但是,if語(yǔ)句在這里只簡(jiǎn)單地檢查了這個(gè)值不是null。實(shí)際上,字符串、數(shù)值還有其他很多值都可以通過(guò)這里的檢查,結(jié)果就會(huì)導(dǎo)致錯(cuò)誤。

現(xiàn)實(shí)當(dāng)中,單純比較null通常是不夠的。檢查值的類型就要真的檢查類型,而不是檢查它不能是什么。例如,在前面的代碼中,values參數(shù)應(yīng)該是數(shù)組。為此,應(yīng)該檢查它到底是不是數(shù)組,而不是檢查它不是null??梢韵裣旅孢@樣重寫那個(gè)函數(shù):

  1. function sortArray(values) { 
  2.   if (values instanceof Array) { // 推薦 
  3.     values.sort(comparator); 
  4.   } 

這個(gè)函數(shù)的這個(gè)版本可以過(guò)濾所有無(wú)效的值,根本不需要使用null。

  • 如果看到比較null的代碼,可以使用下列某種技術(shù)替換它。
  • 如果值應(yīng)該是引用類型,使用instanceof操作符檢查其構(gòu)造函數(shù)。
  • 如果值應(yīng)該是原始類型,使用typeof檢查其類型。
  • 如果希望值是有特定方法名的對(duì)象,使用typeof操作符確保對(duì)象上存在給定名字的對(duì)象。

代碼中比較null的地方越少,就越容易明確類型檢查的目的,從而消除不必要的錯(cuò)誤。

4.4 使用常量

依賴常量的目標(biāo)是從應(yīng)用邏輯中分離數(shù)據(jù),以便修改數(shù)據(jù)時(shí)不會(huì)引發(fā)錯(cuò)誤。顯示在用戶界面上的字符串就應(yīng)該以這種方式提取出來(lái),可以方便實(shí)現(xiàn)國(guó)際化。URL也應(yīng)該這樣提取出來(lái),因?yàn)殡S著應(yīng)用越來(lái)越復(fù)雜,URL也極有可能變化?;旧?,像這種地方因?yàn)檫@種或那種原因?qū)?lái)需要修改時(shí),可能就要找到某個(gè)函數(shù),然后修改其中的代碼。而每次像這樣修改應(yīng)用邏輯,都可能引入新錯(cuò)誤。為此,可以把這些可能會(huì)修改的數(shù)據(jù)提取出來(lái),放在單獨(dú)定義的常量中,以實(shí)現(xiàn)數(shù)據(jù)與邏輯分離。

關(guān)鍵在于把數(shù)據(jù)從使用它們的邏輯中分離出來(lái)??梢允褂靡韵聵?biāo)準(zhǔn)檢查哪些數(shù)據(jù)需要提取。

  • 重復(fù)出現(xiàn)的值。任何使用超過(guò)一次的值都應(yīng)該提取到常量中。這樣可以消除一個(gè)值改了而另一個(gè)值沒(méi)改造成的錯(cuò)誤。這里也包括CSS的類名。
  • 用戶界面字符串。任何會(huì)顯示給用戶的字符串都應(yīng)該提取出來(lái),以方便實(shí)現(xiàn)國(guó)際化。
  • URL:Web應(yīng)用中資源的地址經(jīng)常會(huì)發(fā)生變化,因此建議把所有URL集中放在一個(gè)地方管理。
  • 任何可能變化的值。任何時(shí)候,只要在代碼中使用字面值,就問(wèn)問(wèn)自己這個(gè)值將來(lái)有沒(méi)有可能會(huì)變。如果答案是有可能,那么就應(yīng)該把它提取到常量中。

使用常量是企業(yè)級(jí)JavaScript的重要技術(shù),因?yàn)樗梢宰尨a更容易維護(hù),同時(shí)可以讓代碼免受數(shù)據(jù)變化的影響。

責(zé)任編輯:趙寧寧 來(lái)源: 奇舞周刊
相關(guān)推薦

2022-06-06 00:43:35

系統(tǒng)架構(gòu)設(shè)計(jì)

2024-10-30 08:08:45

2023-06-29 00:19:51

2023-10-16 09:30:06

Java代碼

2021-04-15 08:08:48

微前端Web開(kāi)發(fā)

2023-04-28 14:54:57

架構(gòu)開(kāi)發(fā)React

2024-04-16 08:48:14

WPF開(kāi)發(fā)MVVM庫(kù)Prism

2022-10-20 10:02:16

前端測(cè)試開(kāi)發(fā)

2025-02-13 00:28:26

2023-10-17 09:19:34

開(kāi)發(fā)Java

2021-03-11 14:33:28

Kubernetes開(kāi)源容器

2023-01-27 14:53:03

2020-04-30 21:30:18

JavaScript前端技術(shù)

2024-04-18 08:39:57

依賴注入控制反轉(zhuǎn)WPF

2018-08-03 09:00:00

編程語(yǔ)言Python外部庫(kù)

2021-02-20 10:26:00

前端

2023-07-17 13:57:05

2017-08-24 17:05:06

2023-09-20 23:03:40

C++函數(shù)

2017-07-25 12:09:10

機(jī)器學(xué)習(xí)預(yù)測(cè)性維護(hù)模型
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)