才華橫溢的Stoyan Stefanov,在他寫的由O'Reilly初版的新書《JavaScript Patterns》(JavaScript模式)中,我想要是為我們的讀者貢獻其摘要,那會是件很美妙的事情。具體一點就是編寫高質(zhì)量JavaScript的一些要素,例如避免全局變量,使用單變量聲明,在循環(huán)中預(yù)緩存length(長度),遵循代碼閱讀,以及更多。
此摘要也包括一些與代碼不太相關(guān)的習(xí)慣,但對整體代碼的創(chuàng)建息息相關(guān),包括撰寫API文檔、執(zhí)行同行評審以及運行JSLint。這些習(xí)慣和最佳做法可以幫助你寫出更好的,更易于理解和維護的代碼,這些代碼在幾個月或是幾年之后再回過頭看看也是會覺得很自豪的。
書寫可維護的代碼(Writing Maintainable Code )
軟件bug的修復(fù)是昂貴的,并且隨著時間的推移,這些bug的成本也會增加,尤其當這些bug潛伏并慢慢出現(xiàn)在已經(jīng)發(fā)布的軟件中時。當你發(fā)現(xiàn)bug 的時候就立即修復(fù)它是最好的,此時你代碼要解決的問題在你腦中還是很清晰的。否則,你轉(zhuǎn)移到其他任務(wù),忘了那個特定的代碼,一段時間后再去查看這些代碼就 需要:
還有問題,特別對于大的項目或是公司,修復(fù)bug的這位伙計不是寫代碼的那個人(且發(fā)現(xiàn)bug和修復(fù)bug的不是同一個人)。因此,必須降低理解代 碼花費的時間,無論是一段時間前你自己寫的代碼還是團隊中的其他成員寫的代碼。這關(guān)系到底線(營業(yè)收入)和開發(fā)人員的幸福,因為我們更應(yīng)該去開發(fā)新的激動 人心的事物而不是花幾小時幾天的時間去維護遺留代碼。
另一個相關(guān)軟件開發(fā)生命的事實是,讀代碼花費的時間要比寫來得多。有時候,當你專注并深入思考某個問題的時候,你可以坐下來,一個下午寫大量的代碼。
你的代碼很能很快就工作了,但是,隨著應(yīng)用的成熟,還會有很多其他的事情發(fā)生,這就要求你的進行進行審查,修改,和調(diào)整。例如:
由于這些變化,很少人力數(shù)小時寫的代碼最終演變成花數(shù)周來閱讀這些代碼。這就是為什么創(chuàng)建可維護的代碼對應(yīng)用程序的成功至關(guān)重要。
可維護的代碼意味著:
最小全局變量(Minimizing Globals)
JavaScript通過函數(shù)管理作用域。在函數(shù)內(nèi)部聲明的變量只在這個函數(shù)內(nèi)部,函數(shù)外面不可用。另一方面,全局變量就是在任何函數(shù)外面聲明的或是未聲明直接簡單使用的。
每個JavaScript環(huán)境有一個全局對象,當你在任意的函數(shù)外面使用this的時候可以訪問到。你創(chuàng)建的每一個全部變量都成了這個全局對象的屬 性。在瀏覽器中,方便起見,該全局對象有個附加屬性叫做window,此window(通常)指向該全局對象本身。下面的代碼片段顯示了如何在瀏覽器環(huán)境 中創(chuàng)建和訪問的全局變量:
myglobal = "hello"; // 不推薦寫法console.log(myglobal); // "hello"console.log(window.myglobal); // "hello"console.log(window["myglobal"]); // "hello"console.log(this.myglobal); // "hello"
全局變量的問題
全局變量的問題在于,你的JavaScript應(yīng)用程序和web頁面上的所有代碼都共享了這些全局變量,他們住在同一個全局命名空間,所以當程序的兩個不同部分定義同名但不同作用的全局變量的時候,命名沖突在所難免。
web頁面包含不是該頁面開發(fā)者所寫的代碼也是比較常見的,例如:
比方說,該第三方腳本定義了一個全局變量,叫做result;接著,在你的函數(shù)中也定義一個名為result的全局變量。其結(jié)果就是后面的變量覆蓋前面的,第三方腳本就一下子嗝屁啦!
因此,要想和其他腳本成為好鄰居的話,盡可能少的使用全局變量是很重要的。在書中后面提到的一些減少全局變量的策略,例如命名空間模式或是函數(shù)立即自動執(zhí)行,但是要想讓全局變量少最重要的還是始終使用var來聲明變量。
由于JavaScript的兩個特征,不自覺地創(chuàng)建出全局變量是出乎意料的容易。首先,你可以甚至不需要聲明就可以使用變量;第二,JavaScript有隱含的全局概念,意味著你不聲明的任何變量都會成為一個全局對象屬性。參考下面的代碼:
function sum(x, y) { // 不推薦寫法: 隱式全局變量 result = x + y; return result;}此段代碼中的result沒有聲明。代碼照樣運作正常,但在調(diào)用函數(shù)后你最后的結(jié)果就多一個全局命名空間,這可以是一個問題的根源。
經(jīng)驗法則是始終使用var聲明變量,正如改進版的sum()函數(shù)所演示的:
function sum(x, y) { var result = x + y; return result;}另一個創(chuàng)建隱式全局變量的反例就是使用任務(wù)鏈進行部分var聲明。下面的片段中,a是本地變量但是b確實全局變量,這可能不是你希望發(fā)生的:
// 反例,勿使用 function foo() { var a = b = 0; // ...}此現(xiàn)象發(fā)生的原因在于這個從右到左的賦值,首先,是賦值表達式b = 0,此情況下b是未聲明的。這個表達式的返回值是0,然后這個0就分配給了通過var定義的這個局部變量a。換句話說,就好比你輸入了:
var a = (b = 0);如果你已經(jīng)準備好聲明變量,使用鏈分配是比較好的做法,不會產(chǎn)生任何意料之外的全局變量,如:function foo() { var a, b; // ... a = b = 0; // 兩個均局部變量}然而,另外一個避免全局變量的原因是可移植性。如果你想你的代碼在不同的環(huán)境下(主機下)運行,使用全局變量如履薄冰,因為你會無意中覆蓋你最初環(huán)境下不存在的主機對象(所以你原以為名稱可以放心大膽地使用,實際上對于有些情況并不適用)。
忘記var的副作用(Side Effects When Forgetting var)
隱式全局變量和明確定義的全局變量間有些小的差異,就是通過delete操作符讓變量未定義的能力。
這表明,在技術(shù)上,隱式全局變量并不是真正的全局變量,但它們是全局對象的屬性。屬性是可以通過delete操作符刪除的,而變量是不能的:
// 定義三個全局變量var global_var = 1;global_novar = 2; // 反面教材(function () { global_fromfunc = 3; // 反面教材}());// 試圖刪除delete global_var; // falsedelete global_novar; // truedelete global_fromfunc; // true// 測試該刪除typeof global_var; // "number"typeof global_novar; // "undefined"typeof global_fromfunc; // "undefined"在ES5嚴格模式下,未聲明的變量(如在前面的代碼片段中的兩個反面教材)工作時會拋出一個錯誤。
訪問全局對象(Access to the Global Object)
在瀏覽器中,全局對象可以通過window屬性在代碼的任何位置訪問(除非你做了些比較出格的事情,像是聲明了一個名為window的局部變量)。但是在其他環(huán)境下,這個方便的屬性可能被叫做其他什么東西(甚至在程序中不可用)。如果你需要在沒有硬編碼的window標識符下訪問全局對象,你可以在任何層級的函數(shù)作用域中做如下操作:
var global = (function () { return this;}());這種方法可以隨時獲得全局對象,因為其在函數(shù)中被當做函數(shù)調(diào)用了(不是通過new構(gòu)造),this總 是指向全局對象。實際上這個病不適用于ECMAScript 5嚴格模式,所以,在嚴格模式下時,你必須采取不同的形式。例如,你正在開發(fā)一個JavaScript庫,你可以將你的代碼包裹在一個即時函數(shù)中,然后從 全局作用域中,傳遞一個引用指向this作為你即時函數(shù)的參數(shù)。
單var形式(Single var Pattern)
在函數(shù)頂部使用單var語句是比較有用的一種形式,其好處在于:
單var形式長得就像下面這個樣子:
function func() { var a = 1, b = 2, sum = a + b, myobject = {}, i, j; // function body...} 您可以使用一個var語句聲明多個變量,并以逗號分隔。像這種初始化變量同時初始化值的做法是很好的。這樣子可以防止邏輯錯誤(所有未初始化但聲明的變量的初始值是undefined)和增加代碼的可讀性。在你看到代碼后,你可以根據(jù)初始化的值知道這些變量大致的用途,例如是要當作對象呢還是當作整數(shù)來使。
你也可以在聲明的時候做一些實際的工作,例如前面代碼中的sum = a + b這個情況,另外一個例子就是當你使用DOM(文檔對象模型)引用時,你可以使用單一的var把DOM引用一起指定為局部變量,就如下面代碼所示的:
function updateElement() { var el = document.getElementById("result"), style = el.style; // 使用el和style干點其他什么事...}預(yù)解析:var散布的問題(Hoisting: A Problem with Scattered vars)
JavaScript中,你可以在函數(shù)的任何位置聲明多個var語句,并且它們就好像是在函數(shù)頂部聲明一樣發(fā)揮作用,這種行為稱為 hoisting(懸置/置頂解析/預(yù)解析)。當你使用了一個變量,然后不久在函數(shù)中又重新聲明的話,就可能產(chǎn)生邏輯錯誤。對于JavaScript,只 要你的變量是在同一個作用域中(同一函數(shù)),它都被當做是聲明的,即使是它在var聲明前使用的時候。看下面這個例子:
// 反例myname = "global"; // 全局變量function func() { alert(myname); // "undefined" var myname = "local"; alert(myname); // "local"}func();在這個例子中,你可能會以為第一個alert彈出的是”global”,第二個彈出”loacl”。這種期許是可以理解的,因為在第一個alert 的時候,myname未聲明,此時函數(shù)肯定很自然而然地看全局變量myname,但是,實際上并不是這么工作的。第一個alert會彈 出”undefined”是因為myname被當做了函數(shù)的局部變量(盡管是之后聲明的),所有的變量聲明當被懸置到函數(shù)的頂部了。因此,為了避免這種混 亂,最好是預(yù)先聲明你想使用的全部變量。
上面的代碼片段執(zhí)行的行為可能就像下面這樣:
myname = "global"; // global variablefunction func() { var myname; // 等同于 -> var myname = undefined; alert(myname); // "undefined" myname = "local"; alert(myname); // "local"}func();為了完整,我們再提一提執(zhí)行層面的稍微復(fù)雜點的東西。代碼處理分兩個階段,第一階段是變量,函數(shù)聲明,以及正常格式的參數(shù)創(chuàng)建,這是一個解析和進入上下文 的階段。第二個階段是代碼執(zhí)行,函數(shù)表達式和不合格的標識符(為聲明的變量)被創(chuàng)建。但是,出于實用的目的,我們就采用了”hoisting”這個概念, 這種ECMAScript標準中并未定義,通常用來描述行為。
for循環(huán)(for Loops)
在for循環(huán)中,你可以循環(huán)取得數(shù)組或是數(shù)組類似對象的值,譬如arguments和HTMLCollection對象。通常的循環(huán)形式如下:
// 次佳的循環(huán)for (var i = 0; i < myarray.length; i++) { // 使用myarray[i]做點什么}這種形式的循環(huán)的不足在于每次循環(huán)的時候數(shù)組的長度都要去獲取下。這回降低你的代碼,尤其當myarray不是數(shù)組,而是一個HTMLCollection對象的時候。
HTMLCollections指的是DOM方法返回的對象,例如:
document.getElementsByName()document.getElementsByClassName()document.getElementsByTagName()
還有其他一些HTMLCollections,這些是在DOM標準之前引進并且現(xiàn)在還在使用的。有:
document.images: 頁面上所有的圖片元素document.links : 所有a標簽元素document.forms : 所有表單document.forms[0].elements : 頁面上第一個表單中的所有域
集合的麻煩在于它們實時查詢基本文檔(HTML頁面)。這意味著每次你訪問任何集合的長度,你要實時查詢DOM,而DOM操作一般都是比較昂貴的。
這就是為什么當你循環(huán)獲取值時,緩存數(shù)組(或集合)的長度是比較好的形式,正如下面代碼顯示的:
for (var i = 0, max = myarray.length; i < max; i++) { // 使用myarray[i]做點什么}這樣,在這個循環(huán)過程中,你只檢索了一次長度值。
在所有瀏覽器下,循環(huán)獲取內(nèi)容時緩存HTMLCollections的長度是更快的,2倍(Safari3)到190倍(IE7)之間。//zxx:此數(shù)據(jù)貌似很老,僅供參考
注意到,當你明確想要修改循環(huán)中的集合的時候(例如,添加更多的DOM元素),你可能更喜歡長度更新而不是常量。
伴隨著單var形式,你可以把變量從循環(huán)中提出來,就像下面這樣:
function looper() { var i = 0, max, myarray = []; // ... for (i = 0, max = myarray.length; i < max; i++) { // 使用myarray[i]做點什么 }}這種形式具有一致性的好處,因為你堅持了單一var形式。不足在于當重構(gòu)代碼的時候,復(fù)制和粘貼整個循環(huán)有點困難。例如,你從一個函數(shù)復(fù)制了一個循環(huán)到另一個函數(shù),你不得不去確定你能夠把i和max引入新的函數(shù)(如果在這里沒有用的話,很有可能你要從原函數(shù)中把它們刪掉)。
最后一個需要對循環(huán)進行調(diào)整的是使用下面表達式之一來替換i++。
i = i + 1i += 1
JSLint提示您這樣做,原因是++和 主站蜘蛛池模板: 灵山县| 娱乐| 衡南县| 彝良县| 郸城县| 长顺县| 达拉特旗| 鄂尔多斯市| 华宁县| 高要市| 柯坪县| 高淳县| 湘潭县| 墨竹工卡县| 汽车| 红原县| 磴口县| 安平县| 南皮县| 清镇市| 许昌县| 武穴市| 卢湾区| 吴堡县| 南汇区| 东阿县| 彰武县| 西乌珠穆沁旗| 田东县| 西吉县| 秦皇岛市| 甘南县| 建昌县| 垣曲县| 双鸭山市| 九江市| 南投县| 石台县| 晋州市| 林口县| 霍邱县|