您好,登錄后才能下訂單哦!
編寫(xiě)高質(zhì)量JavaScript代碼的基本要點(diǎn)是什么,相信很多沒(méi)有經(jīng)驗(yàn)的人對(duì)此束手無(wú)策,為此本文總結(jié)了問(wèn)題出現(xiàn)的原因和解決方法,通過(guò)這篇文章希望你能解決這個(gè)問(wèn)題。
此摘要也包括一些與代碼不太相關(guān)的習(xí)慣,但對(duì)整體代碼的創(chuàng)建息息相關(guān),包括撰寫(xiě)API文檔、執(zhí)行同行評(píng)審以及運(yùn)行JSLint。這些習(xí)慣和做法可以幫助你寫(xiě)出更好的,更易于理解和維護(hù)的代碼,這些代碼在幾個(gè)月或是幾年之后再回過(guò)頭看看也是會(huì)覺(jué)得很自豪的。
書(shū)寫(xiě)可維護(hù)的代碼(Writing Maintainable Code )
軟件bug的修復(fù)是昂貴的,并且隨著時(shí)間的推移,這些bug的成本也會(huì)增加,尤其當(dāng)這些bug潛伏并慢慢出現(xiàn)在已經(jīng)發(fā)布的軟件中時(shí)。當(dāng)你發(fā)現(xiàn)bug 的時(shí)候就立即修復(fù)它是***的,此時(shí)你代碼要解決的問(wèn)題在你腦中還是很清晰的。否則,你轉(zhuǎn)移到其他任務(wù),忘了那個(gè)特定的代碼,一段時(shí)間后再去查看這些代碼就 需要:
◆ 花時(shí)間學(xué)習(xí)和理解這個(gè)問(wèn)題
◆ 化時(shí)間是了解應(yīng)該解決的問(wèn)題代碼
還有問(wèn)題,特別對(duì)于大的項(xiàng)目或是公司,修復(fù)bug的這位伙計(jì)不是寫(xiě)代碼的那個(gè)人(且發(fā)現(xiàn)bug和修復(fù)bug的不是同一個(gè)人)。因此,必須降低理解代 碼花費(fèi)的時(shí)間,無(wú)論是一段時(shí)間前你自己寫(xiě)的代碼還是團(tuán)隊(duì)中的其他成員寫(xiě)的代碼。這關(guān)系到底線(xiàn)(營(yíng)業(yè)收入)和開(kāi)發(fā)人員的幸福,因?yàn)槲覀兏鼞?yīng)該去開(kāi)發(fā)新的激動(dòng) 人心的事物而不是花幾小時(shí)幾天的時(shí)間去維護(hù)遺留代碼。
另一個(gè)相關(guān)軟件開(kāi)發(fā)生命的事實(shí)是,讀代碼花費(fèi)的時(shí)間要比寫(xiě)來(lái)得多。有時(shí)候,當(dāng)你專(zhuān)注并深入思考某個(gè)問(wèn)題的時(shí)候,你可以坐下來(lái),一個(gè)下午寫(xiě)大量的代碼。
你的代碼很能很快就工作了,但是,隨著應(yīng)用的成熟,還會(huì)有很多其他的事情發(fā)生,這就要求你的進(jìn)行進(jìn)行審查,修改,和調(diào)整。例如:
◆ bug是暴露的
◆ 新功能被添加到應(yīng)用程序
◆ 程序在新的環(huán)境下工作(例如,市場(chǎng)上出現(xiàn)新想瀏覽器)
◆ 代碼改變用途
◆ 代碼得完全從頭重新,或移植到另一個(gè)架構(gòu)上或者甚至使用另一種語(yǔ)言
由于這些變化,很少人力數(shù)小時(shí)寫(xiě)的代碼最終演變成花數(shù)周來(lái)閱讀這些代碼。這就是為什么創(chuàng)建可維護(hù)的代碼對(duì)應(yīng)用程序的成功至關(guān)重要。
可維護(hù)的代碼意味著:
◆ 可讀的
◆ 一致的
◆ 可預(yù)測(cè)的
◆ 看上去就像是同一個(gè)人寫(xiě)的
◆ 已記錄
最小全局變量(Minimizing Globals)
JavaScript通過(guò)函數(shù)管理作用域。在函數(shù)內(nèi)部聲明的變量只在這個(gè)函數(shù)內(nèi)部,函數(shù)外面不可用。另一方面,全局變量就是在任何函數(shù)外面聲明的或是未聲明直接簡(jiǎn)單使用的。
每個(gè)JavaScript環(huán)境有一個(gè)全局對(duì)象,當(dāng)你在任意的函數(shù)外面使用this的時(shí)候可以訪(fǎng)問(wèn)到。你創(chuàng)建的每一個(gè)全部變量都成了這個(gè)全局對(duì)象的屬 性。在瀏覽器中,方便起見(jiàn),該全局對(duì)象有個(gè)附加屬性叫做window,此window(通常)指向該全局對(duì)象本身。下面的代碼片段顯示了如何在瀏覽器環(huán)境 中創(chuàng)建和訪(fǎng)問(wèn)的全局變量:
myglobal = "hello"; // 不推薦寫(xiě)法 console.log(myglobal); // "hello" console.log(window.myglobal); // "hello" console.log(window["myglobal"]); // "hello" console.log(this.myglobal); // "hello"
全局變量的問(wèn)題
全局變量的問(wèn)題在于,你的JavaScript應(yīng)用程序和web頁(yè)面上的所有代碼都共享了這些全局變量,他們住在同一個(gè)全局命名空間,所以當(dāng)程序的兩個(gè)不同部分定義同名但不同作用的全局變量的時(shí)候,命名沖突在所難免。
web頁(yè)面包含不是該頁(yè)面開(kāi)發(fā)者所寫(xiě)的代碼也是比較常見(jiàn)的,例如:
◆ 第三方的JavaScript庫(kù)
◆ 廣告方的腳本代碼
◆ 第三方用戶(hù)跟蹤和分析腳本代碼
◆ 不同類(lèi)型的小組件,標(biāo)志和按鈕
比方說(shuō),該第三方腳本定義了一個(gè)全局變量,叫做result;接著,在你的函數(shù)中也定義一個(gè)名為result的全局變量。其結(jié)果就是后面的變量覆蓋前面的,第三方腳本就一下子嗝屁啦!
因此,要想和其他腳本成為好鄰居的話(huà),盡可能少的使用全局變量是很重要的。在書(shū)中后面提到的一些減少全局變量的策略,例如命名空間模式或是函數(shù)立即自動(dòng)執(zhí)行,但是要想讓全局變量少最重要的還是始終使用var來(lái)聲明變量。
由于JavaScript的兩個(gè)特征,不自覺(jué)地創(chuàng)建出全局變量是出乎意料的容易。首先,你可以甚至不需要聲明就可以使用變量;第二,JavaScript有隱含的全局概念,意味著你不聲明的任何變量都會(huì)成為一個(gè)全局對(duì)象屬性。參考下面的代碼:
function sum(x, y) { // 不推薦寫(xiě)法: 隱式全局變量 result = x + y; return result; }
此段代碼中的result沒(méi)有聲明。代碼照樣運(yùn)作正常,但在調(diào)用函數(shù)后你***的結(jié)果就多一個(gè)全局命名空間,這可以是一個(gè)問(wèn)題的根源。
經(jīng)驗(yàn)法則是始終使用var聲明變量,正如改進(jìn)版的sum()函數(shù)所演示的:
function sum(x, y) { var result = x + y; return result; }
另一個(gè)創(chuàng)建隱式全局變量的反例就是使用任務(wù)鏈進(jìn)行部分var聲明。下面的片段中,a是本地變量但是b確實(shí)全局變量,這可能不是你希望發(fā)生的:
// 反例,勿使用 function foo() { var a = b = 0; // ... }
此現(xiàn)象發(fā)生的原因在于這個(gè)從右到左的賦值,首先,是賦值表達(dá)式b = 0,此情況下b是未聲明的。這個(gè)表達(dá)式的返回值是0,然后這個(gè)0就分配給了通過(guò)var定義的這個(gè)局部變量a。換句話(huà)說(shuō),就好比你輸入了:
var a = (b = 0);
如果你已經(jīng)準(zhǔn)備好聲明變量,使用鏈分配是比較好的做法,不會(huì)產(chǎn)生任何意料之外的全局變量,如:
function foo() { var a, b; // ... a = b = 0; // 兩個(gè)均局部變量 }
然而,另外一個(gè)避免全局變量的原因是可移植性。如果你想你的代碼在不同的環(huán)境下(主機(jī)下)運(yùn)行,使用全局變量如履薄冰,因?yàn)槟銜?huì)無(wú)意中覆蓋你最初環(huán)境下不存在的主機(jī)對(duì)象(所以你原以為名稱(chēng)可以放心大膽地使用,實(shí)際上對(duì)于有些情況并不適用)。
忘記var的副作用(Side Effects When Forgetting var)
隱式全局變量和明確定義的全局變量間有些小的差異,就是通過(guò)delete操作符讓變量未定義的能力。
◆ 通過(guò)var創(chuàng)建的全局變量(任何函數(shù)之外的程序中創(chuàng)建)是不能被刪除的。
◆ 無(wú)var創(chuàng)建的隱式全局變量(無(wú)視是否在函數(shù)中創(chuàng)建)是能被刪除的。
這表明,在技術(shù)上,隱式全局變量并不是真正的全局變量,但它們是全局對(duì)象的屬性。屬性是可以通過(guò)delete操作符刪除的,而變量是不能的:
// 定義三個(gè)全局變量 var global_var = 1; global_novar = 2; // 反面教材 (function () { global_fromfunc = 3; // 反面教材 }()); // 試圖刪除 delete global_var; // false delete global_novar; // true delete global_fromfunc; // true // 測(cè)試該刪除 typeof global_var; // "number" typeof global_novar; // "undefined" typeof global_fromfunc; // "undefined"
在ES5嚴(yán)格模式下,未聲明的變量(如在前面的代碼片段中的兩個(gè)反面教材)工作時(shí)會(huì)拋出一個(gè)錯(cuò)誤。
訪(fǎng)問(wèn)全局對(duì)象(Access to the Global Object)
在瀏覽器中,全局對(duì)象可以通過(guò)window屬性在代碼的任何位置訪(fǎng)問(wèn)(除非你做了些比較出格的事情,像是聲明了一個(gè)名為window的局部變量)。但是在其他環(huán)境下,這個(gè)方便的屬性可能被叫做其他什么東西(甚至在程序中不可用)。如果你需要在沒(méi)有硬編碼的window標(biāo)識(shí)符下訪(fǎng)問(wèn)全局對(duì)象,你可以在任何層級(jí)的函數(shù)作用域中做如下操作:
var global = (function () { return this; }());
這種方法可以隨時(shí)獲得全局對(duì)象,因?yàn)槠湓诤瘮?shù)中被當(dāng)做函數(shù)調(diào)用了(不是通過(guò)new構(gòu)造),this總 是指向全局對(duì)象。實(shí)際上這個(gè)病不適用于ECMAScript 5嚴(yán)格模式,所以,在嚴(yán)格模式下時(shí),你必須采取不同的形式。例如,你正在開(kāi)發(fā)一個(gè)JavaScript庫(kù),你可以將你的代碼包裹在一個(gè)即時(shí)函數(shù)中,然后從 全局作用域中,傳遞一個(gè)引用指向this作為你即時(shí)函數(shù)的參數(shù)。
單var形式(Single var Pattern)
在函數(shù)頂部使用單var語(yǔ)句是比較有用的一種形式,其好處在于:
◆ 提供了一個(gè)單一的地方去尋找功能所需要的所有局部變量
◆ 防止變量在定義之前使用的邏輯錯(cuò)誤
◆ 幫助你記住聲明的全局變量,因此較少了全局變量//zxx:此處我自己是有點(diǎn)暈乎的…
◆ 少代碼(類(lèi)型啊傳值啊單線(xiàn)完成)
單var形式長(zhǎng)得就像下面這個(gè)樣子:
function func() { var a = 1, b = 2, sum = a + b, myobject = {}, i, j; // function body... }
您可以使用一個(gè)var語(yǔ)句聲明多個(gè)變量,并以逗號(hào)分隔。像這種初始化變量同時(shí)初始化值的做法是很好的。這樣子可以防止邏輯錯(cuò)誤(所有未初始化但聲明的變量的初始值是undefined)和增加代碼的可讀性。在你看到代碼后,你可以根據(jù)初始化的值知道這些變量大致的用途,例如是要當(dāng)作對(duì)象呢還是當(dāng)作整數(shù)來(lái)使。
你也可以在聲明的時(shí)候做一些實(shí)際的工作,例如前面代碼中的sum = a + b這個(gè)情況,另外一個(gè)例子就是當(dāng)你使用DOM(文檔對(duì)象模型)引用時(shí),你可以使用單一的var把DOM引用一起指定為局部變量,就如下面代碼所示的:
function updateElement() { var el = document.getElementById("result"), style = el.style; // 使用el和style干點(diǎn)其他什么事... }
預(yù)解析:var散布的問(wèn)題(Hoisting: A Problem with Scattered vars)
JavaScript中,你可以在函數(shù)的任何位置聲明多個(gè)var語(yǔ)句,并且它們就好像是在函數(shù)頂部聲明一樣發(fā)揮作用,這種行為稱(chēng)為 hoisting(懸置/置頂解析/預(yù)解析)。當(dāng)你使用了一個(gè)變量,然后不久在函數(shù)中又重新聲明的話(huà),就可能產(chǎn)生邏輯錯(cuò)誤。對(duì)于JavaScript,只 要你的變量是在同一個(gè)作用域中(同一函數(shù)),它都被當(dāng)做是聲明的,即使是它在var聲明前使用的時(shí)候??聪旅孢@個(gè)例子:
// 反例 myname = "global"; // 全局變量 function func() { alert(myname); // "undefined" var myname = "local"; alert(myname); // "local" } func();
在這個(gè)例子中,你可能會(huì)以為***個(gè)alert彈出的是”global”,第二個(gè)彈出”loacl”。這種期許是可以理解的,因?yàn)樵?**個(gè)alert 的時(shí)候,myname未聲明,此時(shí)函數(shù)肯定很自然而然地看全局變量myname,但是,實(shí)際上并不是這么工作的。***個(gè)alert會(huì)彈 出”undefined”是因?yàn)閙yname被當(dāng)做了函數(shù)的局部變量(盡管是之后聲明的),所有的變量聲明當(dāng)被懸置到函數(shù)的頂部了。因此,為了避免這種混 亂,***是預(yù)先聲明你想使用的全部變量。
上面的代碼片段執(zhí)行的行為可能就像下面這樣:
myname = "global"; // global variable function func() { var myname; // 等同于 -> var myname = undefined; alert(myname); // "undefined" myname = "local"; alert(myname); // "local"} func();
為了完整,我們?cè)偬嵋惶釄?zhí)行層面的稍微復(fù)雜點(diǎn)的東西。代碼處理分兩個(gè)階段,***階段是變量,函數(shù)聲明,以及正常格式的參數(shù)創(chuàng)建,這是一個(gè)解析和進(jìn)入上下文 的階段。第二個(gè)階段是代碼執(zhí)行,函數(shù)表達(dá)式和不合格的標(biāo)識(shí)符(為聲明的變量)被創(chuàng)建。但是,出于實(shí)用的目的,我們就采用了”hoisting”這個(gè)概念, 這種ECMAScript標(biāo)準(zhǔn)中并未定義,通常用來(lái)描述行為。
for循環(huán)(for Loops)
在for循環(huán)中,你可以循環(huán)取得數(shù)組或是數(shù)組類(lèi)似對(duì)象的值,譬如arguments和HTMLCollection對(duì)象。通常的循環(huán)形式如下:
// 次佳的循環(huán) for (var i = 0; i < myarray.length; i++) { // 使用myarray[i]做點(diǎn)什么 }
這種形式的循環(huán)的不足在于每次循環(huán)的時(shí)候數(shù)組的長(zhǎng)度都要去獲取下。這回降低你的代碼,尤其當(dāng)myarray不是數(shù)組,而是一個(gè)HTMLCollection對(duì)象的時(shí)候。
HTMLCollections指的是DOM方法返回的對(duì)象,例如:
document.getElementsByName() document.getElementsByClassName() document.getElementsByTagName()
還有其他一些HTMLCollections,這些是在DOM標(biāo)準(zhǔn)之前引進(jìn)并且現(xiàn)在還在使用的。有:
document.images: 頁(yè)面上所有的圖片元素 document.links : 所有a標(biāo)簽元素 document.forms : 所有表單 document.forms[0].elements : 頁(yè)面上***個(gè)表單中的所有域
集合的麻煩在于它們實(shí)時(shí)查詢(xún)基本文檔(HTML頁(yè)面)。這意味著每次你訪(fǎng)問(wèn)任何集合的長(zhǎng)度,你要實(shí)時(shí)查詢(xún)DOM,而DOM操作一般都是比較昂貴的。
這就是為什么當(dāng)你循環(huán)獲取值時(shí),緩存數(shù)組(或集合)的長(zhǎng)度是比較好的形式,正如下面代碼顯示的:
for (var i = 0, max = myarray.length; i < max; i++) { // 使用myarray[i]做點(diǎn)什么 }
這樣,在這個(gè)循環(huán)過(guò)程中,你只檢索了一次長(zhǎng)度值。
在所有瀏覽器下,循環(huán)獲取內(nèi)容時(shí)緩存HTMLCollections的長(zhǎng)度是更快的,2倍(Safari3)到190倍(IE7)之間。//zxx:此數(shù)據(jù)貌似很老,僅供參考
注意到,當(dāng)你明確想要修改循環(huán)中的集合的時(shí)候(例如,添加更多的DOM元素),你可能更喜歡長(zhǎng)度更新而不是常量。
伴隨著單var形式,你可以把變量從循環(huán)中提出來(lái),就像下面這樣:
function looper() { var i = 0, max, myarray = []; // ... for (i = 0, max = myarray.length; i < max; i++) { // 使用myarray[i]做點(diǎn)什么 } }
這種形式具有一致性的好處,因?yàn)槟銏?jiān)持了單一var形式。不足在于當(dāng)重構(gòu)代碼的時(shí)候,復(fù)制和粘貼整個(gè)循環(huán)有點(diǎn)困難。例如,你從一個(gè)函數(shù)復(fù)制了一個(gè)循環(huán)到另一個(gè)函數(shù),你不得不去確定你能夠把i和max引入新的函數(shù)(如果在這里沒(méi)有用的話(huà),很有可能你要從原函數(shù)中把它們刪掉)。
***一個(gè)需要對(duì)循環(huán)進(jìn)行調(diào)整的是使用下面表達(dá)式之一來(lái)替換i++。
ii = i + 1 i += 1
JSLint提示您這樣做,原因是++和–-促進(jìn)了“過(guò)分棘手(excessive trickiness)”。//zxx:這里比較難翻譯,我想本意應(yīng)該是讓代碼變得更加的棘手
如果你直接無(wú)視它,JSLint的plusplus選項(xiàng)會(huì)是false(默認(rèn)是default)。
還有兩種變化的形式,其又有了些微改進(jìn),因?yàn)椋?/p>
◆ 少了一個(gè)變量(無(wú)max)
◆ 向下數(shù)到0,通常更快,因?yàn)楹?做比較要比和數(shù)組長(zhǎng)度或是其他不是0的東西作比較更有效率
//***種變化的形式: var i, myarray = []; for (i = myarray.length; i–-;) { // 使用myarray[i]做點(diǎn)什么 } //第二種使用while循環(huán): var myarray = [], i = myarray.length; while (i–-) { // 使用myarray[i]做點(diǎn)什么 }
這些小的改進(jìn)只體現(xiàn)在性能上,此外JSLint會(huì)對(duì)使用i–-加以抱怨。
for-in循環(huán)(for-in Loops)
for-in循環(huán)應(yīng)該用在非數(shù)組對(duì)象的遍歷上,使用for-in進(jìn)行循環(huán)也被稱(chēng)為“枚舉”。
從技術(shù)上將,你可以使用for-in循環(huán)數(shù)組(因?yàn)镴avaScript中數(shù)組也是對(duì)象),但這是不推薦的。因?yàn)槿绻麛?shù)組對(duì)象已被自定義的功能增強(qiáng),就可能發(fā)生邏輯錯(cuò)誤。另外,在for-in中,屬性列表的順序(序列)是不能保證的。所以***數(shù)組使用正常的for循環(huán),對(duì)象使用for-in循環(huán)。
有個(gè)很重要的hasOwnProperty()方法,當(dāng)遍歷對(duì)象屬性的時(shí)候可以過(guò)濾掉從原型鏈上下來(lái)的屬性。
思考下面一段代碼:
// 對(duì)象 var man = { hands: 2, legs: 2, heads: 1 }; // 在代碼的某個(gè)地方 // 一個(gè)方法添加給了所有對(duì)象 if (typeof Object.prototype.clone === "undefined") { Object.prototype.clone = function () {}; }
在這個(gè)例子中,我們有一個(gè)使用對(duì)象字面量定義的名叫man的對(duì)象。在man定義完成后的某個(gè)地方,在對(duì)象原型上增加了一個(gè)很有用的名叫 clone()的方法。此原型鏈?zhǔn)菍?shí)時(shí)的,這就意味著所有的對(duì)象自動(dòng)可以訪(fǎng)問(wèn)新的方法。為了避免枚舉man的時(shí)候出現(xiàn)clone()方法,你需要應(yīng)用hasOwnProperty()方法過(guò)濾原型屬性。如果不做過(guò)濾,會(huì)導(dǎo)致clone()函數(shù)顯示出來(lái),在大多數(shù)情況下這是不希望出現(xiàn)的。
// 1. // for-in 循環(huán) for (var i in man) { if (man.hasOwnProperty(i)) { // 過(guò)濾 console.log(i, ":", man[i]); } } /* 控制臺(tái)顯示結(jié)果 hands : 2 legs : 2 heads : 1 */ // 2. // 反面例子: // for-in loop without checking hasOwnProperty() for (var i in man) { console.log(i, ":", man[i]); } /* 控制臺(tái)顯示結(jié)果 hands : 2 legs : 2 heads : 1 clone: function() */
另外一種使用hasOwnProperty()的形式是取消Object.prototype上的方法。像是:
for (var i in man) { if (Object.prototype.hasOwnProperty.call(man, i)) { // 過(guò)濾 console.log(i, ":", man[i]); } }
其好處在于在man對(duì)象重新定義hasOwnProperty情況下避免命名沖突。也避免了長(zhǎng)屬性查找對(duì)象的所有方法,你可以使用局部變量“緩存”它。
var i, hasOwn = Object.prototype.hasOwnProperty; for (i in man) { if (hasOwn.call(man, i)) { // 過(guò)濾 console.log(i, ":", man[i]); } }
嚴(yán)格來(lái)說(shuō),不使用hasOwnProperty()并不是一個(gè)錯(cuò)誤。根據(jù)任務(wù)以及你對(duì)代碼的自信程度,你可以跳過(guò)它以提高些許的循環(huán)速度。但是當(dāng)你對(duì)當(dāng)前對(duì)象內(nèi)容(和其原型鏈)不確定的時(shí)候,添加hasOwnProperty()更加保險(xiǎn)些。格式化的變化(通不過(guò)JSLint)會(huì)直接忽略掉花括號(hào),把if語(yǔ)句放到同一行上。其優(yōu)點(diǎn)在于循環(huán)語(yǔ)句讀起來(lái)就像一個(gè)完整的想法(每個(gè)元素都有一個(gè)自己的屬性”X”,使用”X”干點(diǎn)什么):
// 警告: 通不過(guò)JSLint檢測(cè) var i, hasOwn = Object.prototype.hasOwnProperty; for (i in man) if (hasOwn.call(man, i)) { // 過(guò)濾 console.log(i, ":", man[i]); }
(不)擴(kuò)展內(nèi)置原型((Not) Augmenting Built-in Prototypes)
擴(kuò)增構(gòu)造函數(shù)的prototype屬性是個(gè)很強(qiáng)大的增加功能的方法,但有時(shí)候它太強(qiáng)大了。
增加內(nèi)置的構(gòu)造函數(shù)原型(如Object(), Array(), 或Function())挺誘人的,但是這嚴(yán)重降低了可維護(hù)性,因?yàn)樗屇愕拇a變得難以預(yù)測(cè)。使用你代碼的其他開(kāi)發(fā)人員很可能更期望使用內(nèi)置的 JavaScript方法來(lái)持續(xù)不斷地工作,而不是你另加的方法。
另外,屬性添加到原型中,可能會(huì)導(dǎo)致不使用hasOwnProperty屬性時(shí)在循環(huán)中顯示出來(lái),這會(huì)造成混亂。
因此,不增加內(nèi)置原型是***的。你可以指定一個(gè)規(guī)則,僅當(dāng)下面的條件均滿(mǎn)足時(shí)例外:
◆ 可以預(yù)期將來(lái)的ECMAScript版本或是JavaScript實(shí)現(xiàn)將一直將此功能當(dāng)作內(nèi)置方法來(lái)實(shí)現(xiàn)。例如,你可以添加ECMAScript 5中描述的方法,一直到各個(gè)瀏覽器都迎頭趕上。這種情況下,你只是提前定義了有用的方法。
◆ 如果您檢查您的自定義屬性或方法已不存在——也許已經(jīng)在代碼的其他地方實(shí)現(xiàn)或已經(jīng)是你支持的瀏覽器JavaScript引擎部分。
◆ 你清楚地文檔記錄并和團(tuán)隊(duì)交流了變化。
如果這三個(gè)條件得到滿(mǎn)足,你可以給原型進(jìn)行自定義的添加,形式如下:
if (typeof Object.protoype.myMethod !== "function") { Object.protoype.myMethod = function () { // 實(shí)現(xiàn)... }; }
switch模式(switch Pattern)
你可以通過(guò)類(lèi)似下面形式的switch語(yǔ)句增強(qiáng)可讀性和健壯性:
var inspect_me = 0, result = ''; switch (inspect_me) { case 0: result = "zero"; break; case 1: result = "one"; break; default: result = "unknown"; }
這個(gè)簡(jiǎn)單的例子中所遵循的風(fēng)格約定如下:
◆ 每個(gè)case和switch對(duì)齊(花括號(hào)縮進(jìn)規(guī)則除外)
◆ 每個(gè)case中代碼縮進(jìn)
◆ 每個(gè)case以break清除結(jié)束
◆ 避免貫穿(故意忽略break)。如果你非常確信貫穿是***的方法,務(wù)必記錄此情況,因?yàn)閷?duì)于有些閱讀人而言,它們可能看起來(lái)是錯(cuò)誤的。
◆ 以default結(jié)束switch:確保總有健全的結(jié)果,即使無(wú)情況匹配。
避免隱式類(lèi)型轉(zhuǎn)換(Avoiding Implied Typecasting )
JavaScript的變量在比較的時(shí)候會(huì)隱式類(lèi)型轉(zhuǎn)換。這就是為什么一些諸如:false == 0 或 “” == 0 返回的結(jié)果是true。為避免引起混亂的隱含類(lèi)型轉(zhuǎn)換,在你比較值和表達(dá)式類(lèi)型的時(shí)候始終使用===和!==操作符。
var zero = 0; if (zero === false) { // 不執(zhí)行,因?yàn)閦ero為0, 而不是false } // 反面示例 if (zero == false) { // 執(zhí)行了... }
還有另外一種思想觀(guān)點(diǎn)認(rèn)為==就足夠了===是多余的。例如,當(dāng)你使用typeof你就知道它會(huì)返回一個(gè)字符串,所以沒(méi)有使用嚴(yán)格相等的理由。然而,JSLint要求嚴(yán)格相等,它使代碼看上去更有一致性,可以降低代碼閱讀時(shí)的精力消耗。(“==是故意的還是一個(gè)疏漏?”)
避免(Avoiding) eval()
如果你現(xiàn)在的代碼中使用了eval(),記住該咒語(yǔ)“eval()是魔鬼”。此方法接受任意的字符串,并當(dāng)作JavaScript代碼來(lái)處理。當(dāng)有 問(wèn)題的代碼是事先知道的(不是運(yùn)行時(shí)確定的),沒(méi)有理由使用eval()。如果代碼是在運(yùn)行時(shí)動(dòng)態(tài)生成,有一個(gè)更好的方式不使用eval而達(dá)到同樣的目 標(biāo)。例如,用方括號(hào)表示法來(lái)訪(fǎng)問(wèn)動(dòng)態(tài)屬性會(huì)更好更簡(jiǎn)單:
// 反面示例 var property = "name"; alert(eval("obj." + property)); // 更好的 var property = "name"; alert(obj[property]);
使用eval()也帶來(lái)了安全隱患,因?yàn)楸粓?zhí)行的代碼(例如從網(wǎng)絡(luò)來(lái))可能已被篡改。這是個(gè)很常見(jiàn)的反面教材,當(dāng)處理Ajax請(qǐng)求得到的JSON 相應(yīng)的時(shí)候。在這些情況下,***使用JavaScript內(nèi)置方法來(lái)解析JSON相應(yīng),以確保安全和有效。若瀏覽器不支持JSON.parse(),你可 以使用來(lái)自JSON.org的庫(kù)。
同樣重要的是要記住,給setInterval(), setTimeout()和Function()構(gòu)造函數(shù)傳遞字符串,大部分情況下,與使用eval()是類(lèi)似的,因此要避免。在幕后,JavaScript仍需要評(píng)估和執(zhí)行你給程序傳遞的字符串:
// 反面示例 setTimeout("myFunc()", 1000); setTimeout("myFunc(1, 2, 3)", 1000); // 更好的 setTimeout(myFunc, 1000); setTimeout(function () { myFunc(1, 2, 3); }, 1000);
使用新的Function()構(gòu)造就類(lèi)似于eval(),應(yīng)小心接近。這可能是一個(gè)強(qiáng)大的構(gòu)造,但往往被誤用。如果你絕對(duì)必須使用eval(),你 可以考慮使用new Function()代替。有一個(gè)小的潛在好處,因?yàn)樵谛翭unction()中作代碼評(píng)估是在局部函數(shù)作用域中運(yùn)行,所以代碼中任何被評(píng)估的通過(guò)var 定義的變量都不會(huì)自動(dòng)變成全局變量。另一種方法來(lái)阻止自動(dòng)全局變量是封裝eval()調(diào)用到一個(gè)即時(shí)函數(shù)中。
考慮下面這個(gè)例子,這里僅un作為全局變量污染了命名空間。
console.log(typeof un); // "undefined" console.log(typeof deux); // "undefined" console.log(typeof trois); // "undefined" var jsstring = "var un = 1; console.log(un);"; eval(jsstring); // logs "1" jsstring = "var deux = 2; console.log(deux);"; new Function(jsstring)(); // logs "2" jsstring = "var trois = 3; console.log(trois);"; (function () { eval(jsstring); }()); // logs "3" console.log(typeof un); // number console.log(typeof deux); // "undefined" console.log(typeof trois); // "undefined"
另一間eval()和Function構(gòu)造不同的是eval()可以干擾作用域鏈,而Function()更安分守己些。不管你在哪里執(zhí)行 Function(),它只看到全局作用域。所以其能很好的避免本地變量污染。在下面這個(gè)例子中,eval()可以訪(fǎng)問(wèn)和修改它外部作用域中的變量,這是 Function做不來(lái)的(注意到使用Function和new Function是相同的)。
(function () { var local = 1; eval("local = 3; console.log(local)"); // logs "3" console.log(local); // logs "3" }()); (function () { var local = 1; Function("console.log(typeof local);")(); // logs undefined }());
parseInt()下的數(shù)值轉(zhuǎn)換(Number Conversions with parseInt())
使用parseInt()你可以從字符串中獲取數(shù)值,該方法接受另一個(gè)基數(shù)參數(shù),這經(jīng)常省略,但不應(yīng)該。當(dāng)字符串以”0″開(kāi)頭的時(shí)候就有可能會(huì)出問(wèn) 題,例如,部分時(shí)間進(jìn)入表單域,在ECMAScript 3中,開(kāi)頭為”0″的字符串被當(dāng)做8進(jìn)制處理了,但這已在ECMAScript 5中改變了。為了避免矛盾和意外的結(jié)果,總是指定基數(shù)參數(shù)。
var month = "06", year = "09"; month = parseInt(month, 10); year = parseInt(year, 10);
此例中,如果你忽略了基數(shù)參數(shù),如parseInt(year),返回的值將是0,因?yàn)椤?9”被當(dāng)做8進(jìn)制(好比執(zhí)行 parseInt( year, 8 )),而09在8進(jìn)制中不是個(gè)有效數(shù)字。
替換方法是將字符串轉(zhuǎn)換成數(shù)字,包括:
+"08" // 結(jié)果是 8 Number("08") // 8
這些通常快于parseInt(),因?yàn)閜arseInt()方法,顧名思意,不是簡(jiǎn)單地解析與轉(zhuǎn)換。但是,如果你想輸入例如“08 hello”,parseInt()將返回?cái)?shù)字,而其它以NaN告終。
編碼規(guī)范(Coding Conventions)
建立和遵循編碼規(guī)范是很重要的,這讓你的代碼保持一致性,可預(yù)測(cè),更易于閱讀和理解。一個(gè)新的開(kāi)發(fā)者加入這個(gè)團(tuán)隊(duì)可以通讀規(guī)范,理解其它團(tuán)隊(duì)成員書(shū)寫(xiě)的代碼,更快上手干活。
許多激烈的爭(zhēng)論發(fā)生會(huì)議上或是郵件列表上,問(wèn)題往往針對(duì)某些代碼規(guī)范的特定方面(例如代碼縮進(jìn),是Tab制表符鍵還是space空格鍵)。如果你是 你組織中建議采用規(guī)范的,準(zhǔn)備好面對(duì)各種反對(duì)的或是聽(tīng)起來(lái)不同但很強(qiáng)烈的觀(guān)點(diǎn)。要記住,建立和堅(jiān)定不移地遵循規(guī)范要比糾結(jié)于規(guī)范的細(xì)節(jié)重要的多。
縮進(jìn)(Indentation)
代碼沒(méi)有縮進(jìn)基本上就不能讀了。唯一糟糕的事情就是不一致的縮進(jìn),因?yàn)樗瓷先ハ袷亲裱艘?guī)范,但是可能一路上伴隨著混亂和驚奇。重要的是規(guī)范地使用縮進(jìn)。
一些開(kāi)發(fā)人員更喜歡用tab制表符縮進(jìn),因?yàn)槿魏稳硕伎梢哉{(diào)整他們的編輯器以自己喜歡的空格數(shù)來(lái)顯示Tab。有些人喜歡空格——通常四個(gè),這都無(wú)所謂,只要團(tuán)隊(duì)每個(gè)人都遵循同一個(gè)規(guī)范就好了。這本書(shū),例如,使用四個(gè)空格縮進(jìn),這也是JSLint中默認(rèn)的縮進(jìn)。
什么應(yīng)該縮進(jìn)呢?規(guī)則很簡(jiǎn)單——花括號(hào)里面的東西。這就意味著函數(shù)體,循環(huán) (do, while, for, for-in),if,switch,以及對(duì)象字面量中的對(duì)象屬性。下面的代碼就是使用縮進(jìn)的示例:
function outer(a, b) { var c = 1, d = 2, inner; if (a > b) { inner = function () { return { r: c - d }; }; } else { inner = function () { return { r: c + d }; }; } return inner; }
花括號(hào){}(Curly Braces)
花括號(hào)(亦稱(chēng)大括號(hào),下同)應(yīng)總被使用,即使在它們?yōu)榭蛇x的時(shí)候。技術(shù)上將,在in或是for中如果語(yǔ)句僅一條,花括號(hào)是不需要的,但是你還是應(yīng)該總是使用它們,這會(huì)讓代碼更有持續(xù)性和易于更新。
想象下你有一個(gè)只有一條語(yǔ)句的for循環(huán),你可以忽略花括號(hào),而沒(méi)有解析的錯(cuò)誤。
// 糟糕的實(shí)例 for (var i = 0; i < 10; i += 1) alert(i);
但是,如果,后來(lái),主體循環(huán)部分又增加了行代碼?
// 糟糕的實(shí)例 for (var i = 0; i < 10; i += 1) alert(i); alert(i + " is " + (i % 2 ? "odd" : "even"));
第二個(gè)alert已經(jīng)在循環(huán)之外,縮進(jìn)可能欺騙了你。為了長(zhǎng)遠(yuǎn)打算,***總是使用花括號(hào),即時(shí)值一行代碼:
// 好的實(shí)例 for (var i = 0; i < 10; i += 1) { alert(i); }
if條件類(lèi)似:
// 壞 if (true) alert(1); else alert(2); // 好 if (true) { alert(1); } else { alert(2); }
左花括號(hào)的位置(Opening Brace Location)
開(kāi)發(fā)人員對(duì)于左大括號(hào)的位置有著不同的偏好——在同一行或是下一行。
if (true) { alert("It's TRUE!"); } //或 if (true) { alert("It's TRUE!"); }
這個(gè)實(shí)例中,仁者見(jiàn)仁智者見(jiàn)智,但也有個(gè)案,括號(hào)位置不同會(huì)有不同的行為表現(xiàn)。這是因?yàn)榉痔?hào)插入機(jī)制(semicolon insertion mechanism)——JavaScript是不挑剔的,當(dāng)你選擇不使用分號(hào)結(jié)束一行代碼時(shí)JavaScript會(huì)自己幫你補(bǔ)上。這種行為可能會(huì)導(dǎo)致麻 煩,如當(dāng)你返回對(duì)象字面量,而左括號(hào)卻在下一行的時(shí)候:
// 警告: 意外的返回值 function func() { return // 下面代碼不執(zhí)行 { name : "Batman" } }
如果你希望函數(shù)返回一個(gè)含有name屬性的對(duì)象,你會(huì)驚訝。由于隱含分號(hào),函數(shù)返回undefined。前面的代碼等價(jià)于:
// 警告: 意外的返回值 function func() { return undefined; // 下面代碼不執(zhí)行 { name : "Batman" } }
總之,總是使用花括號(hào),并始終把在與之前的語(yǔ)句放在同一行:
function func() { return { name : "Batman" }; }
關(guān)于分號(hào)注:就像使用花括號(hào),你應(yīng)該總是使用分號(hào),即使他們可由JavaScript解析器隱式創(chuàng)建。這不僅促進(jìn)更科學(xué)和更嚴(yán)格的代碼,而且有助于解決存有疑惑的地方,就如前面的例子顯示。
空格(White Space)
空格的使用同樣有助于改善代碼的可讀性和一致性。在寫(xiě)英文句子的時(shí)候,在逗號(hào)和句號(hào)后面會(huì)使用間隔。在JavaScript中,你可以按照同樣的邏輯在列表模樣表達(dá)式(相當(dāng)于逗號(hào))和結(jié)束語(yǔ)句(相對(duì)于完成了“想法”)后面添加間隔。
適合使用空格的地方包括:
◆ for循環(huán)分號(hào)分開(kāi)后的的部分:如for (var i = 0; i < 10; i += 1) {...}
◆ for循環(huán)中初始化的多變量(i和max):for (var i = 0, max = 10; i < max; i += 1) {...}
◆ 分隔數(shù)組項(xiàng)的逗號(hào)的后面:var a = [1, 2, 3];
◆ 對(duì)象屬性逗號(hào)的后面以及分隔屬性名和屬性值的冒號(hào)的后面:var o = {a: 1, b: 2};
◆ 限定函數(shù)參數(shù):myFunc(a, b, c)
◆ 函數(shù)聲明的花括號(hào)的前面:function myFunc() {}
◆ 匿名函數(shù)表達(dá)式function的后面:var myFunc = function () {};
使用空格分開(kāi)所有的操作符和操作對(duì)象是另一個(gè)不錯(cuò)的使用,這意味著在+, -, *, =, <, >, <=, >=, ===, !==, &&, ||, +=等前后都需要空格。
// 寬松一致的間距 // 使代碼更易讀 // 使得更加“透氣” var d = 0, a = b + 1; if (a && b && c) { d = a % c; a += d; } // 反面例子 // 缺失或間距不一 // 使代碼變得疑惑 var d = 0, a = b + 1; if (a&&b&&c) { d=a % c; a+= d; }
***需要注意的一個(gè)空格——花括號(hào)間距。***使用空格:
◆ 函數(shù)、if-else語(yǔ)句、循環(huán)、對(duì)象字面量的左花括號(hào)的前面({)
◆ else或while之間的右花括號(hào)(})
空格使用的一點(diǎn)不足就是增加了文件的大小,但是壓縮無(wú)此問(wèn)題。
有一個(gè)經(jīng)常被忽略的代碼可讀性方面是垂直空格的使用。你可以使用空行來(lái)分隔代碼單元,就像是文學(xué)作品中使用段落分隔一樣。
命名規(guī)范(Naming Conventions)
另一種方法讓你的代碼更具可預(yù)測(cè)性和可維護(hù)性是采用命名規(guī)范。這就意味著你需要用同一種形式給你的變量和函數(shù)命名。
下面是建議的一些命名規(guī)范,你可以原樣采用,也可以根據(jù)自己的喜好作調(diào)整。同樣,遵循規(guī)范要比規(guī)范是什么更重要。
以大寫(xiě)字母寫(xiě)構(gòu)造函數(shù)(Capitalizing Constructors)
JavaScript并沒(méi)有類(lèi),但有new調(diào)用的構(gòu)造函數(shù):
var adam = new Person();
因?yàn)闃?gòu)造函數(shù)仍?xún)H僅是函數(shù),僅看函數(shù)名就可以幫助告訴你這應(yīng)該是一個(gè)構(gòu)造函數(shù)還是一個(gè)正常的函數(shù)。
命名構(gòu)造函數(shù)時(shí)首字母大寫(xiě)具有暗示作用,使用小寫(xiě)命名的函數(shù)和方法不應(yīng)該使用new調(diào)用:
function MyConstructor() {...} function myFunction() {...}
分隔單詞(Separating Words)
當(dāng)你的變量或是函數(shù)名有多個(gè)單詞的時(shí)候,***單詞的分離遵循統(tǒng)一的規(guī)范,有一個(gè)常見(jiàn)的做法被稱(chēng)作“駝峰(Camel)命名法”,就是單詞小寫(xiě),每個(gè)單詞的首字母大寫(xiě)。
對(duì)于構(gòu)造函數(shù),可以使用大駝峰式命名法(upper camel case),如MyConstructor()。對(duì)于函數(shù)和方法名稱(chēng),你可以使用小駝峰式命名法(lower camel case),像是myFunction(), calculateArea()和getFirstName()。
要是變量不是函數(shù)呢?開(kāi)發(fā)者通常使用小駝峰式命名法,但還有另外一種做法就是所有單詞小寫(xiě)以下劃線(xiàn)連接:例如,first_name, favorite_bands, 和 old_company_name,這種標(biāo)記法幫你直觀(guān)地區(qū)分函數(shù)和其他標(biāo)識(shí)——原型和對(duì)象。
ECMAScript的屬性和方法均使用Camel標(biāo)記法,盡管多字的屬性名稱(chēng)是罕見(jiàn)的(正則表達(dá)式對(duì)象的lastIndex和ignoreCase屬性)。
其它命名形式(Other Naming Patterns)
有時(shí),開(kāi)發(fā)人員使用命名規(guī)范來(lái)彌補(bǔ)或替代語(yǔ)言特性。
例如,JavaScript中沒(méi)有定義常量的方法(盡管有些內(nèi)置的像Number, MAX_VALUE),所以開(kāi)發(fā)者都采用全部單詞大寫(xiě)的規(guī)范來(lái)命名這個(gè)程序生命周期中都不會(huì)改變的變量,如:
// 珍貴常數(shù),只可遠(yuǎn)觀(guān) var PI = 3.14, MAX_WIDTH = 800;
還有另外一個(gè)完全大寫(xiě)的慣例:全局變量名字全部大寫(xiě)。全部大寫(xiě)命名全局變量可以加強(qiáng)減小全局變量數(shù)量的實(shí)踐,同時(shí)讓它們易于區(qū)分。
另外一種使用規(guī)范來(lái)模擬功能的是私有成員。雖然可以在JavaScript中實(shí)現(xiàn)真正的私有,但是開(kāi)發(fā)者發(fā)現(xiàn)僅僅使用一個(gè)下劃線(xiàn)前綴來(lái)表示一個(gè)私有屬性或方法會(huì)更容易些??紤]下面的例子:
var person = { getName: function () { return this._getFirst() + ' ' + this._getLast(); }, _getFirst: function () { // ... }, _getLast: function () { // ... } };
在此例中,getName()就表示公共方法,部分穩(wěn)定的API。而_getFirst()和_getLast()則表明了私有。它們?nèi)匀皇钦5墓卜椒?,但是使用下劃線(xiàn)前綴來(lái)警告person對(duì)象的使用者這些方法在下一個(gè)版本中時(shí)不能保證工作的,是不能直接使用的。注意,JSLint有些不鳥(niǎo)下劃線(xiàn)前綴,除非你設(shè)置了noman選項(xiàng)為:false。
下面是一些常見(jiàn)的_private規(guī)范:
◆ 使用尾下劃線(xiàn)表示私有,如name_和getElements_()
◆ 使用一個(gè)下劃線(xiàn)前綴表_protected(保護(hù))屬性,兩個(gè)下劃線(xiàn)前綴表示__private (私有)屬性
◆ Firefox中一些內(nèi)置的變量屬性不屬于該語(yǔ)言的技術(shù)部分,使用兩個(gè)前下劃線(xiàn)和兩個(gè)后下劃線(xiàn)表示,如:__proto__和__parent__。
注釋(Writing Comments)
你必須注釋你的代碼,即使不會(huì)有其他人向你一樣接觸它。通常,當(dāng)你深入研究一個(gè)問(wèn)題,你會(huì)很清楚的知道這個(gè)代碼是干嘛用的,但是,當(dāng)你一周之后再回來(lái)看的時(shí)候,想必也要耗掉不少腦細(xì)胞去搞明白到底怎么工作的。
很顯然,注釋不能走極端:每個(gè)單獨(dú)變量或是單獨(dú)一行。但是,你通常應(yīng)該記錄所有的函數(shù),它們的參數(shù)和返回值,或是任何不尋常的技術(shù)和方法。要想到注 釋可以給你代碼未來(lái)的閱讀者以諸多提示;閱讀者需要的是(不要讀太多的東西)僅注釋和函數(shù)屬性名來(lái)理解你的代碼。例如,當(dāng)你有五六行程序執(zhí)行特定的任務(wù), 如果你提供了一行代碼目的以及為什么在這里的描述的話(huà),閱讀者就可以直接跳過(guò)這段細(xì)節(jié)。沒(méi)有硬性規(guī)定注釋代碼比,代碼的某些部分(如正則表達(dá)式)可能注釋 要比代碼多。
最重要的習(xí)慣,然而也是最難遵守的,就是保持注釋的及時(shí)更新,因?yàn)檫^(guò)時(shí)的注釋比沒(méi)有注釋更加的誤導(dǎo)人。
看完上述內(nèi)容,你們掌握編寫(xiě)高質(zhì)量JavaScript代碼的基本要點(diǎn)是什么的方法了嗎?如果還想學(xué)到更多技能或想了解更多相關(guān)內(nèi)容,歡迎關(guān)注億速云行業(yè)資訊頻道,感謝各位的閱讀!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。