在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ HTML/ 編寫高質(zhì)量 JavaScript 代碼的基本要點
代碼復(fù)用模式(避免篇)
S.O.L.I.D 五大原則之接口隔離原則 ISP
設(shè)計模式之狀態(tài)模式
JavaScript 核心(晉級高手必讀篇)
設(shè)計模式之建造者模式
JavaScript 與 DOM(上)——也適用于新手
設(shè)計模式之中介者模式
設(shè)計模式之裝飾者模式
設(shè)計模式之模板方法
設(shè)計模式之外觀模式
強大的原型和原型鏈
設(shè)計模式之構(gòu)造函數(shù)模式
揭秘命名函數(shù)表達(dá)式
深入理解J avaScript 系列(結(jié)局篇)
執(zhí)行上下文(Execution Contexts)
函數(shù)(Functions)
《你真懂 JavaScript 嗎?》答案詳解
設(shè)計模式之適配器模式
設(shè)計模式之組合模式
設(shè)計模式之命令模式
S.O.L.I.D 五大原則之單一職責(zé) SRP
編寫高質(zhì)量 JavaScript 代碼的基本要點
求值策略
閉包(Closures)
對象創(chuàng)建模式(上篇)
This? Yes,this!
設(shè)計模式之代理模式
變量對象(Variable Object)
S.O.L.I.D 五大原則之里氏替換原則 LSP
面向?qū)ο缶幊讨话憷碚?/span>
設(shè)計模式之單例模式
Function 模式(上篇)
S.O.L.I.D 五大原則之依賴倒置原則 DIP
設(shè)計模式之迭代器模式
立即調(diào)用的函數(shù)表達(dá)式
設(shè)計模式之享元模式
設(shè)計模式之原型模式
根本沒有“JSON 對象”這回事!
JavaScript 與 DOM(下)
面向?qū)ο缶幊讨?ECMAScript 實現(xiàn)
全面解析 Module 模式
對象創(chuàng)建模式(下篇)
設(shè)計模式之職責(zé)鏈模式
S.O.L.I.D 五大原則之開閉原則 OCP
設(shè)計模式之橋接模式
設(shè)計模式之策略模式
設(shè)計模式之觀察者模式
代碼復(fù)用模式(推薦篇)
作用域鏈(Scope Chain)
Function 模式(下篇)
設(shè)計模式之工廠模式

編寫高質(zhì)量 JavaScript 代碼的基本要點

才華橫溢的 Stoyan Stefanov,在他寫的由 O’Reilly 初版的新書《JavaScript Patterns》(JavaScript 模式)中,我想要是為我們的讀者貢獻(xiàn)其摘要,那會是件很美妙的事情。具體一點就是編寫高質(zhì)量 JavaScript 的一些要素,例如避免全局變量,使用單變量聲明,在循環(huán)中預(yù)緩存 length(長度),遵循代碼閱讀,以及更多。

此摘要也包括一些與代碼不太相關(guān)的習(xí)慣,但對整體代碼的創(chuàng)建息息相關(guān),包括撰寫 API 文檔、執(zhí)行同行評審以及運行 JSLint。這些習(xí)慣和最佳做法可以幫助你寫出更好的,更易于理解和維護(hù)的代碼,這些代碼在幾個月或是幾年之后再回過頭看看也是會覺得很自豪的。

書寫可維護(hù)的代碼(Writing Maintainable Code )

軟件 bug 的修復(fù)是昂貴的,并且隨著時間的推移,這些 bug 的成本也會增加,尤其當(dāng)這些 bug 潛伏并慢慢出現(xiàn)在已經(jīng)發(fā)布的軟件中時。當(dāng)你發(fā)現(xiàn) bug 的時候就立即修復(fù)它是最好的,此時你代碼要解決的問題在你腦中還是很清晰的。否則,你轉(zhuǎn)移到其他任務(wù),忘了那個特定的代碼,一段時間后再去查看這些代碼就 需要:

  • 花時間學(xué)習(xí)和理解這個問題
  • 花時間是了解應(yīng)該解決的問題代碼

還有問題,特別對于大的項目或是公司,修復(fù) bug 的這位伙計不是寫代碼的那個人(且發(fā)現(xiàn) bug 和修復(fù) bug 的不是同一個人)。因此,必須降低理解代碼花費的時間,無論是一段時間前你自己寫的代碼還是團(tuán)隊中的其他成員寫的代碼。這關(guān)系到底線(營業(yè)收入)和開發(fā)人員的幸福,因為我們更應(yīng)該去開發(fā)新的激動 人心的事物而不是花幾小時幾天的時間去維護(hù)遺留代碼。

另一個相關(guān)軟件開發(fā)生命的事實是,讀代碼花費的時間要比寫來得多。有時候,當(dāng)你專注并深入思考某個問題的時候,你可以坐下來,一個下午寫大量的代碼。

你的代碼很能很快就工作了,但是,隨著應(yīng)用的成熟,還會有很多其他的事情發(fā)生,這就要求你的進(jìn)行進(jìn)行審查,修改,和調(diào)整。例如:

  • bug 是暴露的
  • 新功能被添加到應(yīng)用程序
  • 程序在新的環(huán)境下工作(例如,市場上出現(xiàn)新想瀏覽器)
  • 代碼改變用途
  • 代碼得完全從頭重新,或移植到另一個架構(gòu)上或者甚至使用另一種語言

由于這些變化,很少人力數(shù)小時寫的代碼最終演變成花數(shù)周來閱讀這些代碼。這就是為什么創(chuàng)建可維護(hù)的代碼對應(yīng)用程序的成功至關(guān)重要。

可維護(hù)的代碼意味著:

  • 可讀的
  • 一致的
  • 可預(yù)測的
  • 看上去就像是同一個人寫的
  • 已記錄

最小全局變量(Minimizing Globals)

JavaScript 通過函數(shù)管理作用域。在函數(shù)內(nèi)部聲明的變量只在這個函數(shù)內(nèi)部,函數(shù)外面不可用。另一方面,全局變量就是在任何函數(shù)外面聲明的或是未聲明直接簡單使用的。

每個 JavaScript 環(huán)境有一個全局對象,當(dāng)你在任意的函數(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 頁面上的所有代碼都共享了這些全局變量,他們住在同一個全局命名空間,所以當(dāng)程序的兩個不同部分定義同名但不同作用的全局變量的時候,命名沖突在所難免。

web 頁面包含不是該頁面開發(fā)者所寫的代碼也是比較常見的,例如:

  • 第三方的 JavaScript 庫
  • 廣告方的腳本代碼
  • 第三方用戶跟蹤和分析腳本代碼
  • 不同類型的小組件,標(biāo)志和按鈕

比方說,該第三方腳本定義了一個全局變量,叫做 result;接著,在你的函數(shù)中也定義一個名為 result 的全局變量。其結(jié)果就是后面的變量覆蓋前面的,第三方腳本就一下子嗝屁啦!

因此,要想和其他腳本成為好鄰居的話,盡可能少的使用全局變量是很重要的。在書中后面提到的一些減少全局變量的策略,例如命名空間模式或是函數(shù)立即自動執(zhí)行,但是要想讓全局變量少最重要的還是始終使用 var 來聲明變量。

由于 JavaScript 的兩個特征,不自覺地創(chuàng)建出全局變量是出乎意料的容易。首先,你可以甚至不需要聲明就可以使用變量;第二,JavaScrip t有隱含的全局概念,意味著你不聲明的任何變量都會成為一個全局對象屬性。參考下面的代碼:

function sum(x, y) {
   // 不推薦寫法: 隱式全局變量 
   result = x + y;
   return result;
}

此段代碼中的 result 沒有聲明。代碼照樣運作正常,但在調(diào)用函數(shù)后你最后的結(jié)果就多一個全局命名空間,這可以是一個問題的根源。

經(jīng)驗法則是始終使用 var 聲明變量,正如改進(jìn)版的 sum()函數(shù)所演示的:

function sum(x, y) {
   var result = x + y;
   return result;
}

另一個創(chuàng)建隱式全局變量的反例就是使用任務(wù)鏈進(jìn)行部分 var 聲明。下面的片段中,a 是本地變量但是 b 確實全局變量,這可能不是你希望發(fā)生的:

// 反例,勿使用 
function foo() {
   var a = b = 0;
   // ...
}

此現(xiàn)象發(fā)生的原因在于這個從右到左的賦值,首先,是賦值表達(dá)式 b = 0,此情況下 b 是未聲明的。這個表達(dá)式的返回值是 0,然后這個 0 就分配給了通過 var 定義的這個局部變量 a。換句話說,就好比你輸入了:

var a = (b = 0);

如果你已經(jīng)準(zhǔn)備好聲明變量,使用鏈分配是比較好的做法,不會產(chǎn)生任何意料之外的全局變量,如:

function foo() {
   var a, b;
   // ... a = b = 0; // 兩個均局部變量
}

然而,另外一個避免全局變量的原因是可移植性。如果你想你的代碼在不同的環(huán)境下(主機下)運行,使用全局變量如履薄冰,因為你會無意中覆蓋你最初環(huán)境下不存在的主機對象(所以你原以為名稱可以放心大膽地使用,實際上對于有些情況并不適用)。

忘記 var 的副作用(Side Effects When Forgetting var)

隱式全局變量和明確定義的全局變量間有些小的差異,就是通過 delete 操作符讓變量未定義的能力。

  • 通過 var 創(chuàng)建的全局變量(任何函數(shù)之外的程序中創(chuàng)建)是不能被刪除的。
  • 無var創(chuàng)建的隱式全局變量(無視是否在函數(shù)中創(chuàng)建)是能被刪除的。

這表明,在技術(shù)上,隱式全局變量并不是真正的全局變量,但它們是全局對象的屬性。屬性是可以通過 delete 操作符刪除的,而變量是不能的:

// 定義三個全局變量
var global_var = 1;
global_novar = 2; // 反面教材
(function () {
   global_fromfunc = 3; // 反面教材
}());
// 試圖刪除
delete global_var; // false
delete global_novar; // true
delete global_fromfunc; // true
// 測試該刪除
typeof global_var; // "number"
typeof global_novar; // "undefined"
typeof global_fromfunc; // "undefined"

在 ES5 嚴(yán)格模式下,未聲明的變量(如在前面的代碼片段中的兩個反面教材)工作時會拋出一個錯誤。

訪問全局對象(Access to the Global Object)

在瀏覽器中,全局對象可以通過 window 屬性在代碼的任何位置訪問(除非你做了些比較出格的事情,像是聲明了一個名為 window 的局部變量)。但是在其他環(huán)境下,這個方便的屬性可能被叫做其他什么東西(甚至在程序中不可用)。如果你需要在沒有硬編碼的 window 標(biāo)識符下訪問全局對象,你可以在任何層級的函數(shù)作用域中做如下操作:

var global = (function () {
   return this;
}());

這種方法可以隨時獲得全局對象,因為其在函數(shù)中被當(dāng)做函數(shù)調(diào)用了(不是通過 new 構(gòu)造),this 總 是指向全局對象。實際上這個病不適用于 ECMAScript 5 嚴(yán)格模式,所以,在嚴(yán)格模式下時,你必須采取不同的形式。例如,你正在開發(fā)一個 JavaScript 庫,你可以將你的代碼包裹在一個即時函數(shù)中,然后從 全局作用域中,傳遞一個引用指向 this 作為你即時函數(shù)的參數(shù)。

單 var 形式(Single var Pattern)

在函數(shù)頂部使用單 var 語句是比較有用的一種形式,其好處在于:

  • 提供了一個單一的地方去尋找功能所需要的所有局部變量
  • 防止變量在定義之前使用的邏輯錯誤
  • 幫助你記住聲明的全局變量,因此較少了全局變量//zxx:此處我自己是有點暈乎的…
  • 少代碼(類型啊傳值啊單線完成)

單 var 形式長得就像下面這個樣子:

function func() {
   var a = 1,
       b = 2,
       sum = a + b,
       myobject = {},
       i,
       j;
   // function body...
}

您可以使用一個 var 語句聲明多個變量,并以逗號分隔。像這種初始化變量同時初始化值的做法是很好的。這樣子可以防止邏輯錯誤(所有未初始化但聲明的變量的初始值是 undefined)和增加代碼的可讀性。在你看到代碼后,你可以根據(jù)初始化的值知道這些變量大致的用途,例如是要當(dāng)作對象呢還是當(dāng)作整數(shù)來使。

你也可以在聲明的時候做一些實際的工作,例如前面代碼中的 sum = a + b 這個情況,另外一個例子就是當(dāng)你使用 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ù)解析)。當(dāng)你使用了一個變量,然后不久在函數(shù)中又重新聲明的話,就可能產(chǎn)生邏輯錯誤。對于 JavaScript,只 要你的變量是在同一個作用域中(同一函數(shù)),它都被當(dāng)做是聲明的,即使是它在 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 被當(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();

為了完整,我們再提一提執(zhí)行層面的稍微復(fù)雜點的東西。代碼處理分兩個階段,第一階段是變量,函數(shù)聲明,以及正常格式的參數(shù)創(chuàng)建,這是一個解析和進(jìn)入上下文 的階段。第二個階段是代碼執(zhí)行,函數(shù)表達(dá)式和不合格的標(biāo)識符(為聲明的變量)被創(chuàng)建。但是,出于實用的目的,我們就采用了“hoisting”這個概念, 這種 ECMAScript 標(biāo)準(zhǔn)中并未定義,通常用來描述行為。

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ù)組的長度都要去獲取下。這回降低你的代碼,尤其當(dāng) myarray 不是數(shù)組,而是一個 HTMLCollection 對象的時候。

HTMLCollections 指的是 DOM 方法返回的對象,例如:

document.getElementsByName()
document.getElementsByClassName()
document.getElementsByTagName()

還有其他一些 HTMLCollections,這些是在 DOM 標(biāo)準(zhǔn)之前引進(jìn)并且現(xiàn)在還在使用的。有:

document.images: 頁面上所有的圖片元素
document.links : 所有a標(biāo)簽元素
document.forms : 所有表單
document.forms[0].elements : 頁面上第一個表單中的所有域

集合的麻煩在于它們實時查詢基本文檔(HTML 頁面)。這意味著每次你訪問任何集合的長度,你要實時查詢 DOM,而 DOM 操作一般都是比較昂貴的。

這就是為什么當(dāng)你循環(huán)獲取值時,緩存數(shù)組(或集合)的長度是比較好的形式,正如下面代碼顯示的:

for (var i = 0, max = myarray.length; i < max; i++) {
   // 使用myarray[i]做點什么
}

這樣,在這個循環(huán)過程中,你只檢索了一次長度值。

在所有瀏覽器下,循環(huán)獲取內(nèi)容時緩存 HTMLCollection s 的長度是更快的,2 倍(Safari3)到 190 倍(IE7)之間。//zxx:此數(shù)據(jù)貌似很老,僅供參考

注意到,當(dāng)你明確想要修改循環(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 形式。不足在于當(dāng)重構(gòu)代碼的時候,復(fù)制和粘貼整個循環(huán)有點困難。例如,你從一個函數(shù)復(fù)制了一個循環(huán)到另一個函數(shù),你不得不去確定你能夠把 i 和 max 引入新的函數(shù)(如果在這里沒有用的話,很有可能你要從原函數(shù)中把它們刪掉)。

最后一個需要對循環(huán)進(jìn)行調(diào)整的是使用下面表達(dá)式之一來替換 i++。

i = i + 1
i += 1

JSLint 提示您這樣做,原因是++和–-促進(jìn)了“過分棘手(excessive trickiness)”。//zxx:這里比較難翻譯,我想本意應(yīng)該是讓代碼變得更加的棘手

如果你直接無視它,JSLint 的 plusplus 選項會是 false(默認(rèn)是 default)。

還有兩種變化的形式,其又有了些微改進(jìn),因為:

  • 少了一個變量(無 max)
  • 向下數(shù)到 0,通常更快,因為和 0 做比較要比和數(shù)組長度或是其他不是 0 的東西作比較更有效率
//第一種變化的形式:
var i, myarray = [];
for (i = myarray.length; i–-;) {
   // 使用myarray[i]做點什么
}
//第二種使用while循環(huán):
var myarray = [],
    i = myarray.length;
while (i–-) {
// 使用myarray[i]做點什么
}

這些小的改進(jìn)只體現(xiàn)在性能上,此外 JSLint 會對使用 i–-加以抱怨。

for-in 循環(huán)(for-in Loops)

for-in 循環(huán)應(yīng)該用在非數(shù)組對象的遍歷上,使用 for-in 進(jìn)行循環(huán)也被稱為“枚舉”。

從技術(shù)上將,你可以使用 for-in 循環(huán)數(shù)組(因為 JavaScript 中數(shù)組也是對象),但這是不推薦的。因為如果數(shù)組對象已被自定義的功能增強,就可能發(fā)生邏輯錯誤。另外,在 for-in 中,屬性列表的順序(序列)是不能保證的。所以最好數(shù)組使用正常的 for 循環(huán),對象使用 for-in 循環(huán)。

有個很重要的 hasOwnProperty()方法,當(dāng)遍歷對象屬性的時候可以過濾掉從原型鏈上下來的屬性。

思考下面一段代碼:

// 對象
var man = {
   hands: 2,
   legs: 2,
   heads: 1
};
// 在代碼的某個地方
// 一個方法添加給了所有對象
if (typeof Object.prototype.clone === "undefined") {
   Object.prototype.clone = function () {};
}

在這個例子中,我們有一個使用對象字面量定義的名叫 man 的對象。在 man 定義完成后的某個地方,在對象原型上增加了一個很有用的名叫 clone()的方法。此原型鏈?zhǔn)菍崟r的,這就意味著所有的對象自動可以訪問新的方法。為了避免枚舉 man 的時候出現(xiàn) clone()方法,你需要應(yīng)用 hasOwnProperty()方法過濾原型屬性。如果不做過濾,會導(dǎo)致 clone()函數(shù)顯示出來,在大多數(shù)情況下這是不希望出現(xiàn)的。

// 1.
// for-in 循環(huán)
for (var i in man) {
   if (man.hasOwnProperty(i)) { // 過濾
      console.log(i, ":", man[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]);
}
/*
控制臺顯示結(jié)果
hands : 2
legs : 2
heads : 1
clone: function()
*/

另外一種使用 hasOwnProperty()的形式是取消 Object.prototype 上的方法。像是:

for (var i in man) {
   if (Object.prototype.hasOwnProperty.call(man, i)) { // 過濾
      console.log(i, ":", man[i]);
   }
}

其好處在于在 man 對象重新定義 hasOwnProperty 情況下避免命名沖突。也避免了長屬性查找對象的所有方法,你可以使用局部變量“緩存”它。

var i, hasOwn = Object.prototype.hasOwnProperty;
for (i in man) {
    if (hasOwn.call(man, i)) { // 過濾
        console.log(i, ":", man[i]);
    }
}

嚴(yán)格來說,不使用 hasOwnProperty()并不是一個錯誤。根據(jù)任務(wù)以及你對代碼的自信程度,你可以跳過它以提高些許的循環(huán)速度。但是當(dāng)你對當(dāng)前對象內(nèi)容(和其原型鏈)不確定的時候,添加 hasOwnProperty()更加保險些。

格式化的變化(通不過 JSLint)會直接忽略掉花括號,把 if 語句放到同一行上。其優(yōu)點在于循環(huán)語句讀起來就像一個完整的想法(每個元素都有一個自己的屬性”X”,使用”X”干點什么):

// 警告: 通不過JSLint檢測
var i, hasOwn = Object.prototype.hasOwnProperty;
for (i in man) if (hasOwn.call(man, i)) { // 過濾
    console.log(i, ":", man[i]);
}

(不)擴展內(nèi)置原型((Not) Augmenting Built-in Prototypes)

擴增構(gòu)造函數(shù)的 prototype 屬性是個很強大的增加功能的方法,但有時候它太強大了。

增加內(nèi)置的構(gòu)造函數(shù)原型(如 Object(), Array(),,或Function())挺誘人的,但是這嚴(yán)重降低了可維護(hù)性,因為它讓你的代碼變得難以預(yù)測。使用你代碼的其他開發(fā)人員很可能更期望使用內(nèi)置的 JavaScript 方法來持續(xù)不斷地工作,而不是你另加的方法。

另外,屬性添加到原型中,可能會導(dǎo)致不使用 hasOwnProperty 屬性時在循環(huán)中顯示出來,這會造成混亂。

因此,不增加內(nèi)置原型是最好的。你可以指定一個規(guī)則,僅當(dāng)下面的條件均滿足時例外:

  • 可以預(yù)期將來的 ECMAScript 版本或是 JavaScript 實現(xiàn)將一直將此功能當(dāng)作內(nèi)置方法來實現(xiàn)。例如,你可以添加 ECMAScript 5 中描述的方法,一直到各個瀏覽器都迎頭趕上。這種情況下,你只是提前定義了有用的方法。
  • 如果您檢查您的自定義屬性或方法已不存在——也許已經(jīng)在代碼的其他地方實現(xiàn)或已經(jīng)是你支持的瀏覽器 JavaScript 引擎部分。
  • 你清楚地文檔記錄并和團(tuán)隊交流了變化。

如果這三個條件得到滿足,你可以給原型進(jìn)行自定義的添加,形式如下:

if (typeof Object.protoype.myMethod !== "function") {
   Object.protoype.myMethod = function () {
      // 實現(xiàn)...
   };
}

switch 模式(switch Pattern)

你可以通過類似下面形式的 switch 語句增強可讀性和健壯性:

var inspect_me = 0,
    result = '';
switch (inspect_me) {
case 0:
   result = "zero";
   break;
case 1:
   result = "one";
   break;
default:
   result = "unknown";
}

這個簡單的例子中所遵循的風(fēng)格約定如下:

  • 每個 case 和 switch 對齊(花括號縮進(jìn)規(guī)則除外)
  • 每個 case 中代碼縮進(jìn)
  • 每個 case 以 break 清除結(jié)束
  • 避免貫穿(故意忽略 break)。如果你非常確信貫穿是最好的方法,務(wù)必記錄此情況,因為對于有些閱讀人而言,它們可能看起來是錯誤的。
  • 以 default 結(jié)束 switch:確??傆薪∪慕Y(jié)果,即使無情況匹配。

避免隱式類型轉(zhuǎn)換(Avoiding Implied Typecasting

JavaScript 的變量在比較的時候會隱式類型轉(zhuǎn)換。這就是為什么一些諸如:false == 0 或 “” == 0 返回的結(jié)果是 true。為避免引起混亂的隱含類型轉(zhuǎn)換,在你比較值和表達(dá)式類型的時候始終使用===和!==操作符。

var zero = 0;
if (zero === false) {
   // 不執(zhí)行,因為zero為0, 而不是false
}
// 反面示例
if (zero == false) {
   // 執(zhí)行了...
}

還有另外一種思想觀點認(rèn)為==就足夠了===是多余的。例如,當(dāng)你使用 typeof 你就知道它會返回一個字符串,所以沒有使用嚴(yán)格相等的理由。然而,JSLint 要求嚴(yán)格相等,它使代碼看上去更有一致性,可以降低代碼閱讀時的精力消耗。(“==是故意的還是一個疏漏?”)

避免(Avoiding) eval()

如果你現(xiàn)在的代碼中使用了 eval(),記住該咒語“eval()是魔鬼”。此方法接受任意的字符串,并當(dāng)作 JavaScript 代碼來處理。當(dāng)有 問題的代碼是事先知道的(不是運行時確定的),沒有理由使用 eval()。如果代碼是在運行時動態(tài)生成,有一個更好的方式不使用 eval 而達(dá)到同樣的目標(biāo)。例如,用方括號表示法來訪問動態(tài)屬性會更好更簡單:

// 反面示例
var property = "name";
alert(eval("obj." + property));
// 更好的
var property = "name";
alert(obj[property]);

使用 eval()也帶來了安全隱患,因為被執(zhí)行的代碼(例如從網(wǎng)絡(luò)來)可能已被篡改。這是個很常見的反面教材,當(dāng)處理 Ajax 請求得到的 JSON 相應(yīng)的時候。在這些情況下,最好使用 JavaScript 內(nèi)置方法來解析 JSON 相應(yīng),以確保安全和有效。若瀏覽器不支持 JSON.parse(),你可 以使用來自 JSON.org的庫。

同樣重要的是要記住,給 setInterval(), setTimeout()和 Function()構(gòu)造函數(shù)傳遞字符串,大部分情況下,與使用 eval()是類似的,因此要避免。在幕后,JavaScrip t仍需要評估和執(zhí)行你給程序傳遞的字符串:

// 反面示例
setTimeout("myFunc()", 1000);
setTimeout("myFunc(1, 2, 3)", 1000);
// 更好的
setTimeout(myFunc, 1000);
setTimeout(function () {
   myFunc(1, 2, 3);
}, 1000);

使用新的 Function()構(gòu)造就類似于 eval(),應(yīng)小心接近。這可能是一個強大的構(gòu)造,但往往被誤用。如果你絕對必須使用 eval(),你 可以考慮使用 new Function()代替。有一個小的潛在好處,因為在新 Function()中作代碼評估是在局部函數(shù)作用域中運行,所以代碼中任何被評估的通過 var 定義的變量都不會自動變成全局變量。另一種方法來阻止自動全局變量是封裝 eval()調(diào)用到一個即時函數(shù)中。

考慮下面這個例子,這里僅 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(),它只看到全局作用域。所以其能很好的避免本地變量污染。在下面這個例子中,eval()可以訪問和修改它外部作用域中的變量,這是 Function做不來的(注意到使用 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ù)值,該方法接受另一個基數(shù)參數(shù),這經(jīng)常省略,但不應(yīng)該。當(dāng)字符串以”0″開頭的時候就有可能會出問 題,例如,部分時間進(jìn)入表單域,在 ECMAScript 3 中,開頭為”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,因為“09”被當(dāng)做 8 進(jìn)制(好比執(zhí)行 parseInt( year, 8 )),而 09 在 8 進(jìn)制中不是個有效數(shù)字。

替換方法是將字符串轉(zhuǎn)換成數(shù)字,包括:

+"08" // 結(jié)果是 8
Number("08") // 8

這些通常快于 parseInt(),因為 parseInt()方法,顧名思意,不是簡單地解析與轉(zhuǎn)換。但是,如果你想輸入例如“08 hello”,parseInt()將返回數(shù)字,而其它以 NaN 告終。

編碼規(guī)范(Coding Conventions)

建立和遵循編碼規(guī)范是很重要的,這讓你的代碼保持一致性,可預(yù)測,更易于閱讀和理解。一個新的開發(fā)者加入這個團(tuán)隊可以通讀規(guī)范,理解其它團(tuán)隊成員書寫的代碼,更快上手干活。

許多激烈的爭論發(fā)生會議上或是郵件列表上,問題往往針對某些代碼規(guī)范的特定方面(例如代碼縮進(jìn),是 Tab 制表符鍵還是 space 空格鍵)。如果你是 你組織中建議采用規(guī)范的,準(zhǔn)備好面對各種反對的或是聽起來不同但很強烈的觀點。要記住,建立和堅定不移地遵循規(guī)范要比糾結(jié)于規(guī)范的細(xì)節(jié)重要的多。

縮進(jìn)(Indentation)

代碼沒有縮進(jìn)基本上就不能讀了。唯一糟糕的事情就是不一致的縮進(jìn),因為它看上去像是遵循了規(guī)范,但是可能一路上伴隨著混亂和驚奇。重要的是規(guī)范地使用縮進(jìn)。

一些開發(fā)人員更喜歡用 tab 制表符縮進(jìn),因為任何人都可以調(diào)整他們的編輯器以自己喜歡的空格數(shù)來顯示 Tab。有些人喜歡空格——通常四個,這都無所謂,只要團(tuán)隊每個人都遵循同一個規(guī)范就好了。這本書,例如,使用四個空格縮進(jìn),這也是 JSLint 中默認(rèn)的縮進(jìn)。

什么應(yīng)該縮進(jìn)呢?規(guī)則很簡單——花括號里面的東西。這就意味著函數(shù)體,循環(huán) (do,while,for, for-in),if,switch,以及對象字面量中的對象屬性。下面的代碼就是使用縮進(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;
}

花括號{}(Curly Braces)

花括號(亦稱大括號,下同)應(yīng)總被使用,即使在它們?yōu)榭蛇x的時候。技術(shù)上將,在 in 或是 for 中如果語句僅一條,花括號是不需要的,但是你還是應(yīng)該總是使用它們,這會讓代碼更有持續(xù)性和易于更新。

想象下你有一個只有一條語句的 for 循環(huán),你可以忽略花括號,而沒有解析的錯誤。

// 糟糕的實例
for (var i = 0; i < 10; i += 1)
   alert(i);

但是,如果,后來,主體循環(huán)部分又增加了行代碼?

// 糟糕的實例
for (var i = 0; i < 10; i += 1)
   alert(i);
   alert(i + " is " + (i % 2 ? "odd" : "even"));

第二個 alert 已經(jīng)在循環(huán)之外,縮進(jìn)可能欺騙了你。為了長遠(yuǎn)打算,最好總是使用花括號,即時值一行代碼:

// 好的實例
for (var i = 0; i < 10; i += 1) {
   alert(i);
}

if條件類似:

// 壞
if (true)
   alert(1);
else
   alert(2);
// 好
if (true) {
   alert(1);
} else {
   alert(2);
}

左花括號的位置(Opening Brace Location)

開發(fā)人員對于左大括號的位置有著不同的偏好——在同一行或是下一行。

if (true) {
   alert("It's TRUE!");
}
//或
if (true)
{
   alert("It's TRUE!");
}

這個實例中,仁者見仁智者見智,但也有個案,括號位置不同會有不同的行為表現(xiàn)。這是因為分號插入機制(semicolon insertion mechanism)——JavaScript 是不挑剔的,當(dāng)你選擇不使用分號結(jié)束一行代碼時 JavaScript 會自己幫你補上。這種行為可能會導(dǎo)致麻 煩,如當(dāng)你返回對象字面量,而左括號卻在下一行的時候:

// 警告: 意外的返回值
function func() {
   return
  // 下面代碼不執(zhí)行
   {
      name : "Batman"
   }
}

如果你希望函數(shù)返回一個含有 name 屬性的對象,你會驚訝。由于隱含分號,函數(shù)返回 undefined。前面的代碼等價于:

// 警告: 意外的返回值
function func() {
   return undefined;
  // 下面代碼不執(zhí)行
   {
      name : "Batman"
   }
}

總之,總是使用花括號,并始終把在與之前的語句放在同一行:

function func() {
   return {
      name : "Batman"
   };
}

關(guān)于分號注:就像使用花括號,你應(yīng)該總是使用分號,即使他們可由 JavaScript 解析器隱式創(chuàng)建。這不僅促進(jìn)更科學(xué)和更嚴(yán)格的代碼,而且有助于解決存有疑惑的地方,就如前面的例子顯示。

空格(White Space)

空格的使用同樣有助于改善代碼的可讀性和一致性。在寫英文句子的時候,在逗號和句號后面會使用間隔。在 JavaScript 中,你可以按照同樣的邏輯在列表模樣表達(dá)式(相當(dāng)于逗號)和結(jié)束語句(相對于完成了“想法”)后面添加間隔。

適合使用空格的地方包括:

  • for循環(huán)分號分開后的的部分:如 for (var i = 0; i < 10; i += 1) {...};
  • for 循環(huán)中初始化的多變量(i 和 max):for (var i = 0, max = 10; i < max; i += 1) {...};
  • 分隔數(shù)組項的逗號的后面:var a = [1, 2, 3];
  • 對象屬性逗號的后面以及分隔屬性名和屬性值的冒號的后面:var o = {a: 1, b: 2};
  • 限定函數(shù)參數(shù):myFunc(a, b, c);
  • 函數(shù)聲明的花括號的前面:function myFunc() {};
  • 匿名函數(shù)表達(dá)式function的后面:var myFunc = function () {};

使用空格分開所有的操作符和操作對象是另一個不錯的使用,這意味著在+,-,*,=,<,>,<=,>=,===,!==,&&,||,+=等前后都需要空格。

// 寬松一致的間距
// 使代碼更易讀
// 使得更加“透氣”
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;
}

最后需要注意的一個空格——花括號間距。最好使用空格:

  • 函數(shù)、if-else 語句、循環(huán)、對象字面量的左花括號的前面({)
  • else 或 while 之間的右花括號(})

空格使用的一點不足就是增加了文件的大小,但是壓縮無此問題。

有一個經(jīng)常被忽略的代碼可讀性方面是垂直空格的使用。你可以使用空行來分隔代碼單元,就像是文學(xué)作品中使用段落分隔一樣。

命名規(guī)范(Naming Conventions)

另一種方法讓你的代碼更具可預(yù)測性和可維護(hù)性是采用命名規(guī)范。這就意味著你需要用同一種形式給你的變量和函數(shù)命名。

下面是建議的一些命名規(guī)范,你可以原樣采用,也可以根據(jù)自己的喜好作調(diào)整。同樣,遵循規(guī)范要比規(guī)范是什么更重要。

以大寫字母寫構(gòu)造函數(shù)(Capitalizing Constructors)

JavaScript 并沒有類,但有 new 調(diào)用的構(gòu)造函數(shù):

var adam = new Person();  

因為構(gòu)造函數(shù)仍僅僅是函數(shù),僅看函數(shù)名就可以幫助告訴你這應(yīng)該是一個構(gòu)造函數(shù)還是一個正常的函數(shù)。

命名構(gòu)造函數(shù)時首字母大寫具有暗示作用,使用小寫命名的函數(shù)和方法不應(yīng)該使用new調(diào)用:

function MyConstructor() {...}
function myFunction() {...}

分隔單詞(Separating Words)

當(dāng)你的變量或是函數(shù)名有多個單詞的時候,最好單詞的分離遵循統(tǒng)一的規(guī)范,有一個常見的做法被稱作“駝峰(Camel)命名法”,就是單詞小寫,每個單詞的首字母大寫。

對于構(gòu)造函數(shù),可以使用大駝峰式命名法(upper camel case),如MyConstructor()。對于函數(shù)和方法名稱,你可以使用小駝峰式命名法(lower camel case),像是 myFunction(), calculateArea()和 getFirstName()。

要是變量不是函數(shù)呢?開發(fā)者通常使用小駝峰式命名法,但還有另外一種做法就是所有單詞小寫以下劃線連接:例如,first_name, favorite_bands,和 old_company_name,這種標(biāo)記法幫你直觀地區(qū)分函數(shù)和其他標(biāo)識——原型和對象。

ECMAScript 的屬性和方法均使用 Camel標(biāo)記法,盡管多字的屬性名稱是罕見的(正則表達(dá)式對象的 lastIndex 和 ignoreCase 屬性)。

其它命名形式(Other Naming Patterns)

有時,開發(fā)人員使用命名規(guī)范來彌補或替代語言特性。

例如,JavaScript 中沒有定義常量的方法(盡管有些內(nèi)置的像 Number,MAX_VALUE),所以開發(fā)者都采用全部單詞大寫的規(guī)范來命名這個程序生命周期中都不會改變的變量,如:

// 珍貴常數(shù),只可遠(yuǎn)觀
var PI = 3.14,
    MAX_WIDTH = 800;

還有另外一個完全大寫的慣例:全局變量名字全部大寫。全部大寫命名全局變量可以加強減小全局變量數(shù)量的實踐,同時讓它們易于區(qū)分。

另外一種使用規(guī)范來模擬功能的是私有成員。雖然可以在 JavaScript 中實現(xiàn)真正的私有,但是開發(fā)者發(fā)現(xiàn)僅僅使用一個下劃線前綴來表示一個私有屬性或方法會更容易些??紤]下面的例子:

var person = {
    getName: function () {
        return this._getFirst() + ' ' + this._getLast();
    },
    _getFirst: function () {
        // ...
    },
    _getLast: function () {
        // ...
    }
};

在此例中,getName()就表示公共方法,部分穩(wěn)定的 API。而_getFirst()和_getLast()則表明了私有。它們?nèi)匀皇钦5墓卜椒?,但是使用下劃線前綴來警告 person 對象的使用者這些方法在下一個版本中時不能保證工作的,是不能直接使用的。注意,JSLint有些不鳥下劃線前綴,除非你設(shè)置了 noman 選項為:false。

下面是一些常見的_private 規(guī)范:

  • 使用尾下劃線表示私有,如 name和 getElements\()
  • 使用一個下劃線前綴表protected(保護(hù))屬性,兩個下劃線前綴表示\_private (私有)屬性
  • Firefox 中一些內(nèi)置的變量屬性不屬于該語言的技術(shù)部分,使用兩個前下劃線和兩個后下劃線表示,如__proto__和__parent__。

注釋(Writing Comments)

你必須注釋你的代碼,即使不會有其他人向你一樣接觸它。通常,當(dāng)你深入研究一個問題,你會很清楚的知道這個代碼是干嘛用的,但是,當(dāng)你一周之后再回來看的時候,想必也要耗掉不少腦細(xì)胞去搞明白到底怎么工作的。

很顯然,注釋不能走極端:每個單獨變量或是單獨一行。但是,你通常應(yīng)該記錄所有的函數(shù),它們的參數(shù)和返回值,或是任何不尋常的技術(shù)和方法。要想到注 釋可以給你代碼未來的閱讀者以諸多提示;閱讀者需要的是(不要讀太多的東西)僅注釋和函數(shù)屬性名來理解你的代碼。例如,當(dāng)你有五六行程序執(zhí)行特定的任務(wù), 如果你提供了一行代碼目的以及為什么在這里的描述的話,閱讀者就可以直接跳過這段細(xì)節(jié)。沒有硬性規(guī)定注釋代碼比,代碼的某些部分(如正則表達(dá)式)可能注釋 要比代碼多。

最重要的習(xí)慣,然而也是最難遵守的,就是保持注釋的及時更新,因為過時的注釋比沒有注釋更加的誤導(dǎo)人。

關(guān)于作者(About the Author)

Stoyan Stefanov 是 Yahoo!web 開發(fā)人員,多個O'Reilly書籍的作者、投稿者和技術(shù)評審。他經(jīng)常在會議和他的博客www.phpied.com上發(fā)表 web 開發(fā)主題的演講。Stoyan 還是 smush.it 圖片優(yōu)化工具的創(chuàng)造者,YUI 貢獻(xiàn)者,雅虎性能優(yōu)化工具 YSlow 2.0 的架構(gòu)設(shè)計師。