<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                <a name="a1"></a> # 第二章 高質量JavaScript基本要點 本章將對一些實質內容展開討論,這些內容包括最佳實踐、模式和編寫高質量JavaScript代碼的習慣,比如避免全局變量、使用單var聲明、循環中的length預緩存、遵守編碼約定等等。本章還包括一些非必要的編程習慣,但更多的關注點將放在總體的代碼創建過程上,包括撰寫API文檔、組織相互評審以及使用JSLint。這些習慣和最佳實踐可以幫助你寫出更好的、更易讀的和可維護的代碼,當幾個月后或數年后再重讀你的代碼時,你就會深有體會了。 <a name="a2"></a> ## 編寫可維護的代碼 修復軟件bug成本很高,而且隨著時間的推移,它們造成的損失也越來越大,特別是在已經打包發布了的軟件發現了bug的時候。當然最好是發現bug立刻解決掉,但前提是你對你的代碼依然很熟悉,否則當你轉身投入到另外一個項目的開發中后,根本不記得當初代碼的模樣了。過了一段時間后你再去閱讀當初的代碼你需要: - 時間來重新學習并理解問題 - 時間去理解問題相關的代碼 對大型項目或者公司來說還有一個不得不考慮的問題,就是解決這個bug的人和制造這個bug的人往往不是同一個人。因此減少理解代碼所需的時間成本就顯得非常重要,不管是隔了很長時間重讀自己的代碼還是閱讀團隊內其他人的代碼。這對于公司的利益底線和工程師的幸福指數同樣重要,因為每個人都寧愿去開發新的項目而不愿花很多時間和精力去維護舊代碼。 另外一個軟件開發中的普遍現象是,在讀代碼上花的時間要遠遠超過寫代碼的時間。常常當你專注于某個問題的時候,你會坐下來用一下午的時間產出大量的代碼。當時的場景下代碼是可以正常運行的,但當應用趨于成熟,會有很多因素促使你重讀代碼、改進代碼或對代碼做微調。比如: - 發現了bug - 需要給應用添加新需求 - 需要將應用遷移到新的平臺中運行(比如當市場中出現了新的瀏覽器時) - 代碼重構 - 由于架構更改或者更換另一種語言導致代碼重寫 這些不確定因素帶來的后果是,少數人花幾小時寫的代碼需要很多人花幾個星期去閱讀它。因此,創建可維護的代碼對于一個成功的應用來說至關重要。 可維護的代碼意味著代碼是: - 可讀的 - 一致的 - 可預測的 - 看起來像是同一個人寫的 - 有文檔的 本章接下來的部分會對這幾點深入講解。 <a name="a3"></a> ## 減少全局對象 JavaScript 使用函數來管理作用域,在一個函數內定義的變量稱作“局部變量”,局部變量在函數外部是不可見的。另一方面,“全局變量”是不在任何函數體內部聲明的變量,或者是直接使用而未明的變量。 每一個JavaScript運行環境都有一個“全局對象”,不在任何函數體內使用this就可以獲得對這個全局對象的引用。你所創建的每一個全局變量都是這個全局對象的屬性。為了方便起見,瀏覽器都會額外提供一個全局對象的屬性window,(常常)用以指向全局對象本身。下面的示例代碼中展示了如何在瀏覽器中創建或訪問全局變量: ``` myglobal = "hello"; // antipattern console.log(myglobal); // "hello" console.log(window.myglobal); // "hello" console.log(window["myglobal"]); // "hello" console.log(this.myglobal); // "hello" ``` <a name="a4"></a> ### 全局對象帶來的困擾 全局變量的問題是,它們在JavaScript代碼執行期間或者整個web頁面中始終是可見的。它們存在于同一個命名空間中,因此命名沖突的情況時有發生,畢竟在應用程序的不同模塊中,經常會出于某種目的定義相同的全局變量。 同樣,常常網頁中所嵌入的代碼并不是這個網頁的開發者所寫,比如: - 網頁中使用了第三方的JavaScript庫 - 網頁中使用了廣告代碼 - 網頁中使用了用以分析流量和點擊率的第三方統計代碼 - 網頁中使用了很多組件,掛件和按鈕等等 假設某一段第三方提供的腳本定義了一個全局變量result。隨后你在自己寫的某個函數中也定義了一個全局變量result。這時,第二個變量就會覆蓋第一個,這時就會導致第三方腳本停止工作。 因此,為了讓你的腳本和這個頁面中的其他腳本和諧相處,要盡可能少的使用全局變量,這一點非常重要。本書隨后的章節中會講到一些減少全局變量的技巧和策略,比如使用命名空間或者立即執行的匿名函數等,但減少全局變量最有效的方法是堅持使用var來聲明變量。 由于JavaScript的特點,我們經常有意無意的創建全局變量,畢竟在JavaScript中創建全局變量實在太簡單了。首先,你可以不聲明而直接使用變量,再者,JavaScirpt中具有“隱式全局對象”的概念,也就是說任何不通過var聲明(譯注:在JavaScript1.7及以后的版本中,可以通過let來聲明塊級作用域的變量)的變量都會成為全局對象的一個屬性(可以把它們當作全局變量)。看一下下面這段代碼: ``` function sum(x, y) { // antipattern: implied global result = x + y; return result; } ``` 這段代碼中,我們直接使用了result而沒有事先聲明它。這段代碼是能夠正常工作的,但在調用這個方法之后,會產生一個全局變量result,這會帶來其他問題。 解決辦法是,總是使用var來聲明變量,下面代碼就是改進了的sum()函數: ``` function sum(x, y) { var result = x + y; return result; } ``` 這里我們要注意一種反模式,就是在var聲明中通過鏈式賦值的方法創建全局變量。在下面這個代碼片段中,a是局部變量,但b是全局變量,而作者的意圖顯然不是如此: ``` // antipattern, do not use function foo() { var a = b = 0; // ... } ``` 為什么會這樣?因為這里的計算順序是從右至左的。首先計算表達式b=0,這里的b是未聲明的,這個表達式的值是0,然后通過var創建了局部變量a,并賦值為0。換言之,可以等價的將代碼寫成這樣: ``` var a = (b = 0); ``` 如果變量b已經被聲明,這種鏈式賦值的寫法是ok的,不會意外的創建全局變量,比如: ``` function foo() { var a, b; // ... a = b = 0; // both local } ``` > 避免使用全局變量的另一個原因是出于可移植性考慮的,如果你希望將你的代碼運行于不同的平臺環境(宿主),使用全局變量則非常危險。很有可能你無意間創建的某個全局變量在當前的平臺環境中是不存在的,你認為可以安全的使用,而在其他的環境中卻是存在的。 <a name="a5"></a> ### 忘記var時的副作用 隱式的全局變量和顯式定義的全局變量之間有著細微的差別,差別在于通過delete來刪除它們的時候表現不一致。 - 通過var創建的全局變量(在任何函數體之外創建的變量)不能被刪除。 - 沒有用var創建的隱式全局變量(不考慮函數內的情況)可以被刪除。 也就是說,隱式全局變量并不算是真正的變量,但他們是全局對象的屬性成員。屬性是可以通過delete運算符刪除的,而變量不可以被刪除: >(譯注:在瀏覽器環境中,所有 JavaScript 代碼都是在 window 作用域內的,所以在這種情況下,我們所說的全局變量其實都是 window 下的一個屬性,故可以用 delete 刪除,但在如 nodejs 或 gjs 等非瀏覽器環境下,顯式聲明的全局變量無法用 delete 刪除。) ``` // define three globals var global_var = 1; global_novar = 2; // antipattern (function () { global_fromfunc = 3; // antipattern }()); // attempt to delete delete global_var; // false delete global_novar; // true delete global_fromfunc; // true // test the deletion typeof global_var; // "number" typeof global_novar; // "undefined" typeof global_fromfunc; // "undefined" ``` 在ES5嚴格模式中,給未聲明的變量賦值會報錯(比如這段代碼中提到的兩個反模式)。 <a name="a6"></a> ### 訪問全局對象 在瀏覽器中,我們可以隨時隨地通過window屬性來訪問全局對象(除非你定義了一個名叫window的局部變量)。但換一個運行環境這個方便的window可能就換成了別的名字(甚至根本就被禁止訪問全局對象了)。如果不想通過這種寫死window的方式來得到全局變量,有一個辦法,你可以在任意層次嵌套的函數作用域內執行: ``` var global = (function () { return this; }()); ``` 這種方式總是可以得到全局對象,因為在被當作函數執行的函數體內(而不是被當作構造函數執行的函數體內),this總是指向全局對象。但這種情況在ECMAScript5的嚴格模式中行不通,因此在嚴格模式中你不得不尋求其他的替代方案。比如,如果你在開發一個庫,你會將你的代碼包裝在一個立即執行的匿名函數中(在第四章會講到),然后從全局作用域中給這個匿名函數傳入一個指向this的參數。 <a name="a7"></a> ### 單 var 模式 在函數的頂部使用一個單獨的var語句是非常推薦的一種模式,它有如下一些好處: - 在同一個位置可以查找到函數所需的所有變量 - 避免當在變量聲明之前使用這個變量時產生的邏輯錯誤(參照下一小節“聲明提前:分散的 var 帶來的問題”) - 提醒你不要忘記聲明變量,順便減少潛在的全局變量 - 代碼量更少(輸入更少且更易做代碼優化) 單var模式看起來像這樣: ``` function func() { var a = 1, b = 2, sum = a + b, myobject = {}, i, j; // function body... } ``` 你可以使用一個var語句來聲明多個變量,變量之間用逗號分隔。也可以在這個語句中加入變量的初始化,這是一個非常好的實踐。這種方式可以避免邏輯錯誤(所有未初始化的變量都被聲明了,且值為undefined)并增加了代碼的可讀性。過段時間后再看這段代碼,你會體會到聲明不同類型變量的慣用名稱,比如,你一眼就可看出某個變量是對象還是整數。 你可以在聲明變量時多做一些額外的工作,比如在這個例子中就寫了sum=a+b這種代碼。另一個例子就是當代碼中用到對DOM元素時,你可以把對DOM的引用賦值給一些變量,這一步就可以放在一個單獨的聲明語句中,比如下面這段代碼: ``` function updateElement() { var el = document.getElementById("result"), style = el.style; // do something with el and style... } ``` <a name="a8"></a> ### 聲明提前:分散的 var 帶來的問題 JavaScript 中是允許在函數的任意地方寫任意多個var語句的,其實相當于在函數體頂部聲明變量,這種現象被稱為“變量提前”,當你在聲明之前使用這個變量時,可能會造成邏輯錯誤。對于JavaScript來說,一旦在某個作用域(同一個函數內)里聲明了一個變量,這個變量在整個作用域內都是存在的,包括在var聲明語句之前。看一下這個例子: ``` // antipattern myname = "global"; // global variable function func() { alert(myname); // "undefined" var myname = "local"; alert(myname); // "local" } func(); ``` 這個例子中,你可能期望第一個alert()彈出“global”,第二個alert()彈出“local”。這種結果看起來是合乎常理的,因為在第一個alert執行時,myname還沒有聲明,這時就應該“尋找”全局變量中的myname。但實際情況并不是這樣,第一個alert彈出“undefined”,因為myname已經在函數內有聲明了(盡管聲明語句在后面)。所有的變量聲明都提前到了函數的頂部。因此,為了避免類似帶有“歧義”的程序邏輯,最好在使用之前一起聲明它們。 上一個代碼片段等價于下面這個代碼片段: ``` myname = "global"; // global variable function func() { var myname; // same as -> var myname = undefined; alert(myname); // "undefined" myname = "local"; alert(myname); // "local" } func(); ``` >這里有必要對“變量提前”作進一步補充,實際上從JavaScript引擎的工作機制上看,這個過程稍微有點復雜。代碼處理經過了兩個階段,第一階段是創建變量、函數和參數,這一步是預編譯的過程,它會掃描整段代碼的上下文。第二階段是代碼的運行,這一階段將創建函數表達式和一些非法的標識符(未聲明的變量)。從實用性角度來講,我們更愿意將這兩個階段歸成一個概念“變量提前”,盡管這個概念并沒有在ECMAScript標準中定義,但我們常常用它來解釋預編譯的行為過程。 <a name="a9"></a> ## for 循環 在for循環中,可以對數組或類似數組的對象(比如arguments和HTMLCollection對象)作遍歷,最普通的for循環模式形如: ``` // sub-optimal loop for (var i = 0; i < myarray.length; i++) { // do something with myarray[i] } ``` 這種模式的問題是,每次遍歷都會訪問數組的length屬性。這降低了代碼運行效率,特別是當myarray并不是一個數組而是一個HTMLCollection對象的時候。 HTMLCollection是由DOM方法返回的對象,比如: - document.getElementsByName() - document.getElementsByClassName() - document.getElementsByTagName() 還有很多其他的HTMLCollection,這些對象是在DOM標準之前就已經在用了,這些HTMLCollection主要包括: **document.images** 頁面中所有的IMG元素 **document.links** 頁面中所有的A元素 **document.forms** 頁面中所有的表單 **document.forms[0].elements** 頁面中第一個表單的所有字段 這些對象的問題在于,它們均是指向文檔(HTML頁面)中的活動對象。也就是說每次通過它們訪問集合的length時,總是會去查詢DOM,而DOM操作則是很耗資源的。 更好的辦法是為for循環緩存住要遍歷的數組的長度,比如下面這段代碼: ``` for (var i = 0, max = myarray.length; i < max; i++) { // do something with myarray[i] } ``` 通過這種方法只需要訪問DOM節點一次以獲得length,在整個循環過程中就都可以使用它。 不管在什么瀏覽器中,在遍歷HTMLCollection時緩存length都可以讓程序執行的更快,可以提速兩倍(Safari3)到一百九十倍(IE7)不等。更多細節可以參照Nicholas Zakas的《高性能JavaScript》,這本書也是由O'Reilly出版。 需要注意的是,當你在循環過程中需要修改這個元素集合(比如增加DOM元素)時,你更希望更新length而不是更新常量。 遵照單var模式,你可以將var提到循環的外部,比如: ``` function looper() { var i = 0, max, myarray = []; // ... for (i = 0, max = myarray.length; i < max; i++) { // do something with myarray[i] } } ``` 這種模式帶來的好處就是提高了代碼的一致性,因為你越來越依賴這種單var模式。缺點就是在重構代碼的時候不能直接復制粘貼一個循環體,比如,你正在將某個循環從一個函數拷貝至另外一個函數中,必須確保i和max也拷貝至新函數里,并且需要從舊函數中將這些沒用的變量刪除掉。 最后一個需要對循環做出調整的地方是將i++替換成為下面兩者之一: ``` i = i + 1 i += 1 ``` JSLint提示你這樣做,是因為++和--實際上降低了代碼的可讀性,如果你覺得無所謂,可以將JSLint的plusplus選項設為false(默認為true),本書所介紹的最后一個模式用到了: i += 1。 關于這種for模式還有兩種變化的形式,做了少量改進,原因有二: - 減少一個變量(沒有max) - 減量循環至0,這種方式速度更快,因為和零比較要比和非零數字或數組長度比較要高效的多 第一種變化形式是: ``` var i, myarray = []; for (i = myarray.length; i--;) { // do something with myarray[i] } ``` 第二種變化形式用到了while循環: ``` var myarray = [], i = myarray.length; while (i--) { // do something with myarray[i] } ``` 這些小改進只體現在性能上,此外,JSLint不推薦使用i--。 <a name="a10"></a> ## for-in 循環 for-in 循環用于對非數組對象作遍歷。通過for-in進行循環也被稱作“枚舉”。 從技術角度講,for-in循環同樣可以用于數組(JavaScript中數組即是對象),但不推薦這樣做。當使用自定義函數擴充了數組對象時,這時更容易產生邏輯錯誤。另外,for-in循環中屬性的遍歷順序是不固定的,所以最好數組使用普通的for循環,對象使用for-in循環。 可以使用對象的hasOwnProperty()方法將從原型鏈中繼承來的屬性過濾掉,這一點非常重要。看一下這段代碼: ``` // the object var man = { hands: 2, legs: 2, heads: 1 }; // somewhere else in the code // a method was added to all objects if (typeof Object.prototype.clone === "undefined") { Object.prototype.clone = function () {}; } ``` 在這段例子中,我們定義了一個名叫man的對象直接量。在代碼中的某個地方(可以是man定義之前也可以是之后),給Object的原型中增加了一個方法clone()。原型鏈是實時的,這意味著所有的對象都可以訪問到這個新方法。要想在枚舉man的時候避免枚舉出clone()方法,則需要調用hasOwnProperty()來對原型屬性進行過濾。如果不做過濾,clone()也會被遍歷到,而這不是我們所希望的: ``` // 1. // for-in loop for (var i in man) { if (man.hasOwnProperty(i)) { // filter console.log(i, ":", man[i]); } } /* result in the console hands : 2 legs : 2 heads : 1 */ // 2. // antipattern: // for-in loop without checking hasOwnProperty() for (var i in man) { console.log(i, ":", man[i]); } /* result in the console hands : 2 legs : 2 heads : 1 clone: function() */ ``` 另外一種的寫法是通過Object.prototype直接調用hasOwnProperty()方法,像這樣: ``` for (var i in man) { if (Object.prototype.hasOwnProperty.call(man, i)) { // filter console.log(i, ":", man[i]); } } ``` 這種做法的好處是,當man對象中重新定義了hasOwnProperty方法時,可以避免調用時的命名沖突(譯注:明確指定調用的是Object.prototype上的方法而不是實例對象中的方法),這種做法同樣可以避免冗長的屬性查找過程(譯注:這種查找過程多是在原型鏈上進行查找),一直查找到Object中的方法,你可以定義一個變量來“緩存”住它(譯注:這里所指的是緩存住Object.prototype.hasOwnProperty): ``` var i, hasOwn = Object.prototype.hasOwnProperty; for (i in man) { if (hasOwn.call(man, i)) { // filter console.log(i, ":", man[i]); } } ``` >嚴格說來,省略hasOwnProperty()并不是一個錯誤。根據具體的任務以及你對代碼的自信程度,你可以省略掉它以提高一些程序執行效率。但當你對當前要遍歷的對象不確定的時候,添加hasOwnProperty()則更加保險些。 這里提到一種格式上的變化寫法(這種寫法無法通過JSLint檢查),這種寫法在for循環所在的行加入了if判斷條件,他的好處是能讓循環語句讀起來更完整和通順(“如果元素包含屬性X,則拿X做點什么”): ``` // Warning: doesn't pass JSLint var i, hasOwn = Object.prototype.hasOwnProperty; for (i in man) if (hasOwn.call(man, i)) { // filter console.log(i, ":", man[i]); } ``` <a name="a11"></a> ## (不)擴充內置原型 我們可以擴充構造函數的prototype屬性,這是一種非常強大的特性,用來為構造函數增加功能,但有時這個功能強大到超過我們的掌控。 給內置構造函數比如Object()、Array()、和Function()擴充原型看起來非常誘人,但這種做法嚴重降低了代碼的可維護性,因為它讓你的代碼變得難以預測。對于那些基于你的代碼做開發的開發者來說,他們更希望使用原生的JavaScript方法來保持工作的連續性,而不是使用你所添加的方法(譯注:因為原生的方法更可靠,而你寫的方法可能會有bug)。 另外,如果將屬性添加至原型中,很可能導致在那些不使用hasOwnProperty()做檢測的循環中將原型上的屬性遍歷出來,這會造成混亂。 因此,不擴充內置對象的原型是最好的,你也可以自己定義一個規則,僅當下列條件滿足時做例外考慮: 1. 未來的ECMAScript版本的JavaScirpt會將你實現的方法添加為內置方法。比如,你可以實現ECMAScript5定義的一些方法,一直等到瀏覽器升級至支持ES5。這樣,你只是提前定義了這些有用的方法。 2. 如果你發現你自定義的方法已經不存在,要么已經在代碼其他地方實現了,要么是瀏覽器的JavaScript引擎已經內置實現了。 3. 你所做的擴充附帶充分的文檔說明,且和團隊其他成員做了溝通。 如果你遇到這三種情況之一,你可以給內置原型添加自定義方法,寫法如下: ``` if (typeof Object.protoype.myMethod !== "function") { Object.protoype.myMethod = function () { // implementation... }; } ``` <a name="a12"></a> ## switch 模式 你可以通過下面這種模式的寫法來增強switch語句的可讀性和健壯性: ``` var inspect_me = 0, result = ''; switch (inspect_me) { case 0: result = "zero"; break; case 1: result = "one"; break; default: result = "unknown"; } ``` 這個簡單的例子所遵循的風格約定如下: - 每個case和switch對齊(這里不考慮花括號相關的縮進規則) - 每個case中的代碼整齊縮進 - 每個case都以break作為結束 - 避免連續執行多個case語句塊(當省略break時會發生),如果你堅持認為連續執行多case語句塊是最好的方法,請務必補充文檔說明,對于其他人來說,這種情況看起來是錯誤的。 - 以default結束整個switch,以確保即便是在找不到匹配項時也會有正常的結果, <a name="a13"></a> ## 避免隱式類型轉換 在JavaScript的比較操作中會有一些隱式的數據類型轉換。比如諸如false == 0或""==0之類的比較都返回true。 為了避免隱式類型轉換造對程序造成干擾,推薦使用===和!===運算符,它們較除了比較值還會比較類型。 ``` var zero = 0; if (zero === false) { // not executing because zero is 0, not false } // antipattern if (zero == false) { // this block is executed... } ``` 另外一種觀點認為當==夠用的時候就不必多余的使用===。比如,當你知道typeof的返回值是一個字符串,就不必使用全等運算符。但JSLint卻要求使用全等運算符,這當然會提高代碼風格的一致性,并減少了閱讀代碼時的思考(“這里使用==是故意的還是無意的?”)。 <a name="a14"></a> ### 避免使用eval() 當你想使用eval()的時候,不要忘了那句話“eval()是魔鬼”。這個函數的參數是一個字符串,它可以執行任意字符串。如果事先知道要執行的代碼是有問題的(在運行之前),則沒有理由使用eval()。如果需要在運行時動態生成執行代碼,往往都會有更佳的方式達到同樣的目的,而非一定要使用eval()。例如,訪問動態屬性時可以使用方括號: ``` // antipattern var property = "name"; alert(eval("obj." + property)); // preferred var property = "name"; alert(obj[property]); ``` eval()同樣有安全隱患,因為你需要運行一些容易被干擾的代碼(比如運行一段來自于網絡的代碼)。在處理Ajax請求所返回的JSON數據時會常遇到這種情況,使用eval()是一種反模式。這種情況下最好使用瀏覽器的內置方法來解析JSON數據,以確保代碼的安全性和數據的合法性。如果瀏覽器不支持JSON.parse(),你可以使用JSON.org所提供的庫。 記住,多數情況下,給setInterval()、setTimeout()和Function()構造函數傳入字符串的情形和eval()類似,這種用法也是應當避免的,這一點非常重要,因為這些情形中JavaScript最終還是會執行傳入的字符串參數: ``` // antipatterns setTimeout("myFunc()", 1000); setTimeout("myFunc(1, 2, 3)", 1000); // preferred setTimeout(myFunc, 1000); setTimeout(function () { myFunc(1, 2, 3); }, 1000); ``` new Function()的用法和eval()非常類似,應當特別注意。這種構造函數的方式很強大,但往往被誤用。如果你不得不使用eval(),你可以嘗試用new Function()來代替。這有一個潛在的好處,在new Function()中運行的代碼會在一個局部函數作用域內執行,因此源碼中所有用var定義的變量不會自動變成全局變量。還有一種方法可以避免eval()中定義的變量轉換為全局變量,即是將eval()包裝在一個立即執行的匿名函數內(詳細內容請參照第四章)。 看一下這個例子,這里只有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構造函數還有一個區別,就是eval()可以修改作用域鏈,而Function更像是一個沙箱。不管在什么地方執行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 }()); ``` <a name="a15"></a> ## 使用parseInt()進行數字轉換 可以使用parseInt()將字符串轉換為數字。函數的第二個參數是轉換基數(譯注:“基數”指的是數字進制的方式),這個參數通常被省略。但當字符串以0為前綴時轉換就會出錯,例如,在表單中輸入日期的一個字段。ECMAScript3中以0為前綴的字符串會被當作八進制數處理(基數為8)。但在ES5中不是這樣。為了避免轉換類型不一致而導致的意外結果,應當總是指定第二個參數: ``` var month = "06", year = "09"; month = parseInt(month, 10); year = parseInt(year, 10); ``` 在這個例子中,如果省略掉parseInt的第二個參數,比如parseInt(year),返回值是0,因為“09”被認為是八進制數(等價于parseInt(year,8)),而且09是非法的八進制數。 字符串轉換為數字還有兩種方法: ``` +"08" // result is 8 Number("08") // 8 ``` 這兩種方法要比parseInt()更快一些,因為顧名思義parseInt()是一種“解析”而不是簡單的“轉換”。但當你期望將“08 hello”這類字符串轉換為數字,則必須使用parseInt(),其他方法都會返回NaN。 <a name="a16"></a> ## 編碼風格 確立并遵守編碼規范非常重要,這會讓你的代碼風格一致、可預測、可讀性更強。團隊新成員通過學習編碼規范可以很快進入開發狀態、并寫出團隊其他成員易于理解的代碼。 在開源社區和郵件組中關于編碼風格的爭論一直不斷(比如關于代碼縮進,用tab還是空格?)。因此,如果你打算在團隊內推行某種編碼規范時,要做好應對各種反對意見的心理準備,而且要吸取各種意見,這對確立并一貫遵守某種編碼規范是非常重要,而不是斤斤計較的糾結于編碼規范的細節。 <a name="a17"></a> ### 縮進 代碼沒有縮進幾乎就不能讀了,而不一致的縮進更加糟糕,因為它看上去像是遵循了規范,真正讀起來卻磕磕絆絆。因此規范的使用縮進非常重要。 有些開發者喜歡使用tab縮進,因為每個人都可以根據自己的喜好來調整tab縮進的空格數,有些人則喜歡使用空格縮進,通常是四個空格,這都無所謂,只要團隊每個人都遵守同一個規范即可,本書中所有的示例代碼都采用四個空格的縮進寫法,這也是JSLint所推薦的。 那么到底什么應該縮進呢?規則很簡單,花括號里的內容應當縮進,包括函數體、循環(do、while、for和for-in)體、if條件、switch語句和對象直接量里的屬性。下面的代碼展示了如何正確的使用縮進: ``` 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; } ``` <a name="a18"></a> ### 花括號 應當總是使用花括號,即使是在可省略花括號的時候也應當如此。從技術角度講,如果if或for中只有一個語句,花括號是可以省略的,但最好還是不要省略。這讓你的代碼更加工整一致而且易于更新。 假設有這樣一段代碼,for循環中只有一條語句,你可以省略掉這里的花括號,而且不會有語法錯誤: ``` // bad practice for (var i = 0; i < 10; i += 1) alert(i); ``` 但如果過了一段時間,你給這個循環添加了另一行代碼? ``` // bad practice for (var i = 0; i < 10; i += 1) alert(i); alert(i + " is " + (i % 2 ? "odd" : "even")); ``` 第二個alert實際處于循環體之外,但這里的縮進會迷惑你。長遠考慮最好還是寫上花括號,即便是在只有一個語句的語句塊中也應如此: ``` // better for (var i = 0; i < 10; i += 1) { alert(i); } ``` 同理,if條件句也應當如此: ``` // bad if (true) alert(1); else alert(2); // better if (true) { alert(1); } else { alert(2); } ``` <a name="a19"></a> ### 左花括號的位置 開發人員對于左大括號的位置有著不同的偏好,在同一行呢還是在下一行? ``` if (true) { alert("It's TRUE!"); } ``` 或者: ``` if (true) { alert("It's TRUE!"); } ``` 在這個例子中,看起來只是個人偏好問題。但有時候花括號位置的不同則會影響程序的執行。因為JavaScript會“自動插入分號”。JavaScript對行結束時的分號并無要求,它會自動將分號補全。因此,當函數return語句返回了一個對象直接量,而對象的左花括號和return不在同一行時,程序的執行就和預想的不同了: ``` // warning: unexpected return value function func() { return { name: "Batman" }; } ``` 可以看出程序作者的意圖是返回一個包含了name屬性的對象,但實際情況不是這樣。因為return后會填補一個分號,函數的返回值就是undefined。這段代碼等價于: ``` // warning: unexpected return value function func() { return undefined; // unreachable code follows... { name: "Batman" }; } ``` 結論,總是使用花括號,而且總是將左花括號與上一條語句放在同一行: ``` function func() { return { name: "Batman" }; } ``` >關于分號應當注意:和花括號一樣,應當總是使用分號,盡管在JavaScript解析代碼時會補全行末省略的分號。嚴格遵守這條規則,可以讓代碼更加嚴謹,同時可以避免前面例子中所出現的歧義。 <a name="a20"></a> ### 空格 空格的使用同樣有助于改善代碼的可讀性和一致性。在寫英文句子的時候,在逗號和句號后面會使用間隔。在JavaScript中,你可以按照同樣的邏輯在表達式(相當于逗號)和語句結束(相對于完成了某個“想法”)后面添加間隔。 適合使用空格的地方包括: - for循環中的分號之后,比如 `for (var i = 0; i < 10; i += 1) {...}` - for循環中初始化多個變量,比如 `for (var i = 0, max = 10; i < max; i += 1) {...}` - 分隔數組項的逗號之后,`var a = [1, 2, 3];` - 對象屬性后的逗號以及名值對之間的冒號之后,`var o = {a: 1, b: 2};` - 函數參數中,`myFunc(a, b, c)` - 函數聲明的花括號之前,`function myFunc() {}` - 匿名函數表達式function之后,`var myFunc = function () {};` 另外,我們推薦在運算符和操作數之間添加空格。也就是說在+, -, *, =, <, >, <=, >=, ===, !==, &&, ||, +=符號前后都添加空格。 ``` // generous and consistent spacing // makes the code easier to read // allowing it to "breathe" var d = 0, a = b + 1; if (a && b && c) { d = a % c; a += d; } // antipattern // missing or inconsistent spaces // make the code confusing var d= 0, a =b+1; if (a&& b&&c) { d=a %c; a+= d; } ``` 最后,還應當注意,最好在花括號旁邊添加空格: - 在函數、if-else語句、循環、對象直接量的左花括號之前補充空格({) - 在右花括號和else和while之間補充空格 >垂直空白的使用經常被我們忽略,你可以使用空行來將代碼單元分隔開,就像文學作品中使用段落作分隔一樣。 <a name="a21"></a> ## 命名規范 另外一種可以提升你代碼的可預測性和可維護性的方法是采用命名規范。也就是說變量和函數的命名都遵照同種習慣。 下面是一些建議的命名規范,你可以原樣采用,也可以根據自己的喜好作調整。同樣,遵循規范要比規范本身更加重要。 <a name="a22"></a> ### 構造器命名中的大小寫 JavaScript中沒有類,但有構造函數,可以通過new來調用構造函數: ``` var adam = new Person(); ``` 由于構造函數畢竟還是函數,不管我們將它用作構造器還是函數,當然希望只通過函數名就可分辨出它是構造器還是普通函數。 首字母大寫可以提示你這是一個構造函數,而首字母小寫的函數一般只認為它是普通的函數,不應該通過new來調用它: ``` function MyConstructor() {...} function myFunction() {...} ``` 下一章將介紹一些強制將函數用作構造器的編程模式,但遵守我們所提到的命名規范會更好的幫助程序員閱讀源碼。 <a name="a23"></a> ### 單詞分隔 當你的變量名或函數名中含有多個單詞時,單詞之間的分隔也應當遵循統一的約定。最常見的做法是“駝峰式”命名,單詞都是小寫,每個單詞的首字母是大寫。 對于構造函數,可以使用“大駝峰式”命名,比如MyConstructor(),對于函數和方法,可以采用“小駝峰式”命名,比如myFunction(),calculateArea()和getFirstName()。 那么對于那些不是函數的變量應當如何命名呢?變量名通常采用小駝峰式命名,還有一個不錯的做法是,變量所有字母都是小寫,單詞之間用下劃線分隔,比如,first_name,favorite_bands和old_company_name,這種方法可以幫助你區分函數和其他標識符——原始數據類型或對象。 ECMAScript的屬性和方法均使用Camel標記法,盡管多字的屬性名稱是罕見的(正則表達式對象的lastIndex和ignoreCase屬性)。 在ECMAScript中的屬性和方法均使用駝峰式命名,盡管包含多單詞的屬性名稱(正則表達式對象中的lastIndex和ignoreCase)并不常見。 <a name="a24"></a> ### 其他命名風格 有時開發人員使用命名規范來彌補或代替語言特性的不足。 比如,JavaScript中無法定義常量(盡管有一些內置常量比如Number.MAX_VALUE),所以開發者都采用了這種命名習慣,對于那些程序運行周期內不會更改的變量使用全大寫字母來命名。比如: ``` // precious constants, please don't touch var PI = 3.14, MAX_WIDTH = 800; ``` 除了使用大寫字母的命名方式之外,還有另一種命名規約:全局變量都大寫。這種命名方式和“減少全局變量”的約定相輔相成,并讓全局變量很容易辨認。 除了常量和全局變量的命名慣例,這里討論另外一種命名慣例,即私有變量的命名。盡管在JavaScript是可以實現真正的私有變量的,但開發人員更喜歡在私有成員或方法名之前加上下劃線前綴,比如下面的例子: ``` var person = { getName: function () { return this._getFirst() + ' ' + this._getLast(); }, _getFirst: function () { // ... }, _getLast: function () { // ... } }; ``` 在這個例子中,getName()的身份是一個公有方法,屬于穩定的API,而_getFirst()和_getLast()則是私有方法。盡管這兩個方法本質上和公有方法無異,但在方法名前加下劃線前綴就是為了警告用戶不要直接使用這兩個私有方法,因為不能保證它們在下一個版本中還能正常工作。JSLint會對私有方法作檢查,除非設置了JSLint的nomen選項為false。 下面介紹一些_private風格寫法的變種: - 在名字尾部添加下劃下以表明私有,比如`name_`和`getElements_()` - 使用一個下劃線前綴表明受保護的屬性_protected,用兩個下劃線前綴表明私有屬性__private - 在Firefox中實現了一些非標準的內置屬性,這些屬性在開頭和結束都有兩個下劃線,比如`__proto__`和`__parent__` <a name="a25"></a> ## 書寫注釋 寫代碼就要寫注釋,即便你認為你的代碼不會被別人讀到。當你對一個問題非常熟悉時,你會很快找到問題代碼,但當過了幾個星期后再來讀這段代碼,則需要絞盡腦汁的回想代碼的邏輯。 你不必對顯而易見的代碼作過多的注釋:每個變量和每一行都作注釋。但你需要對所有的函數、他們的參數和返回值補充注釋,對于那些有趣的或怪異的算法和技術也應當配備注釋。對于閱讀你的代碼的其他人來說,注釋就是一種提示,只要閱讀注釋、函數名以及參數,就算不讀代碼也能大概理解程序的邏輯。比如,這里有五到六行代碼完成了某個功能,如果提供了一行描述這段代碼功能的注釋,讀程序的人就不必再去關注代碼的細節實現了。代碼注釋的寫法并沒有硬性規定,有些代碼片段(比如正則表達式)的確需要比代碼本身還多的注釋。 >由于過時的注釋會帶來很多誤導,這比不寫注釋還糟糕。因此保持注釋時刻更新的習慣非常重要,盡管對很多人來說這很難做到。 在下一小節我們會講到,注釋可以自動生成文檔。 <a name="a26"></a> ## 書寫API文檔 很多人都覺得寫文檔是一件枯燥且吃力不討好的事情,但實際情況不是這樣。我們可以通過代碼注釋自動生成文檔,這樣就不用再去專門寫文檔了。很多人覺得這是一個不錯的點子,因為根據某些關鍵字和格式化的文檔自動生成可閱讀的參考手冊本身就是“某種編程”。 傳統的APIdoc誕生自Java世界,這個工具名叫“javadoc”,和Java SDK(軟件開發工具包)一起提供。但這個創意迅速被其他語言借鑒。JavaScript領域有兩個非常優秀的開源工具,它們是JSDoc Toolkit(http://code.google.com/p/jsdoc-toolkit/ )和YUIDoc(http://yuilibrary.com/projects/yuidoc )。 生成API文檔的過程包括: - 以特定的格式來組織書寫源代碼 - 運行工具來對代碼和注釋進行解析 - 發布工具運行的結果,通常是HTML頁面 你需要學習這種特殊的語法,包括十幾種標簽,寫法類似于: ``` /** * @tag value */ ``` 比如這里有一個函數reverse(),可以對字符串進行反序操作。它的參數和返回值都是字符串。給它補充注釋如下: ``` /** * Reverse a string * * @param {String} input String to reverse * @return {String} The reversed string */ var reverse = function (input) { // ... return output; }; ``` 可以看到,@param是用來說明輸入參數的標簽,@return是用來說明返回值的標簽,文檔生成工具最終會為將這種帶注釋的源代碼解析成格式化好的HTML文檔。 <a name="a27"></a> ### 一個例子:YUIDoc YUIDoc最初的目的是為YUI庫(Yahoo! User Interface)生成文檔,但也可以應用于任何項目,為了更充分的使用YUIDoc你需要學習它的注釋規范,比如模塊和類的寫法(當然在JavaScript中是沒有類的概念的)。 讓我們看一個用YUIDoc生成文檔的完整例子。 圖2-1展示了最終生成的文檔的模樣,你可以根據項目需要隨意定制HTML模板,讓生成的文檔更加友好和個性化。 這里同樣提供了在線的demo,請參照 http://jspatterns.com/book/2/。 這個例子中所有的應用作為一個模塊(myapp)放在一個文件里(app.js),后續的章節會更詳細的介紹模塊,現在只需知道用可以用一個YUIDoc的標簽來表示模塊即可。 圖2-1 YUIDoc生成的文檔 ![pic](http://img02.taobaocdn.com/tps/i2/T1fSCgXdBsXXXXXXXX-781-647.png) app.js的開始部分: ``` /** * My JavaScript application * * @module myapp */ ``` 然后定義了一個空對象作為模塊的命名空間: ``` var MYAPP = {}; ``` 緊接著定義了一個包含兩個方法的對象math_stuff,這兩個方法分別是sum()和multi(): ``` /** * A math utility * @namespace MYAPP * @class math_stuff */ MYAPP.math_stuff = { /** * Sums two numbers * * @method sum * @param {Number} a First number * @param {Number} b The second number * @return {Number} The sum of the two inputs */ sum: function (a, b) { return a + b; }, /** * Multiplies two numbers * * @method multi * @param {Number} a First number * @param {Number} b The second number * @return {Number} The two inputs multiplied */ multi: function (a, b) { return a * b; } }; ``` 這樣就結束了第一個“類”的定義,注意粗體表示的標簽。 @namespace 指向你的對象的全局引用 @class 代表一個對象或構造函數的不恰當的稱謂(JavaScript中沒有類) @method 定義對象的方法,并指定方法的名稱 @param 列出函數需要的參數,參數的類型放在一對花括號內,跟隨其后的是參數名和描述 @return 和@param類似,用以描述方法的返回值,可以不帶名字 我們用構造函數來實現第二個“類”,給這個類的原型添加一個方法,能夠體會到YUIDoc采用了不同的方式來創建對象: ``` /** * Constructs Person objects * @class Person * @constructor * @namespace MYAPP * @param {String} first First name * @param {String} last Last name */ MYAPP.Person = function (first, last) { /** * Name of the person * @property first_name * @type String */ this.first_name = first; /** * Last (family) name of the person * @property last_name * @type String */ this.last_name = last; }; /** * Returns the name of the person object * * @method getName * @return {String} The name of the person */ MYAPP.Person.prototype.getName = function () { return this.first_name + ' ' + this.last_name; }; ``` 在圖2-1中可以看到生成的文檔中Person構造函數的生成結果,粗體的部分是: - @constructor 暗示了這個“類”其實是一個構造函數 - @prototype 和 @type 用來描述對象的屬性 YUIDoc工具是語言無關的,只解析注釋塊,而不是JavaScript代碼。它的缺點是必須要在注釋中指定屬性、參數和方法的名字,比如,@property first_name。好處是一旦你熟練掌握YUIDoc,就可以用它對任何語言源碼進行注釋的文檔化。 <a name="a28"></a> ## 編寫易讀的代碼 這種將APIDoc格式的代碼注釋解析成API參考文檔的做法看起來很偷懶,但還有另外一個目的,通過代碼重審來提高代碼質量。 很多作者或編輯會告訴你“編輯非常重要”,甚至是寫一本好書或好文章最最重要的步驟。將想法落實在紙上形成草稿只是第一步,草稿給讀者提的信息往往重點不明晰、結構不合理、或不符合循序漸進的閱讀習慣。 對于編程也是同樣的道理,當你坐下來解決一個問題的時候,這時的解決方案只是一種“草案”,盡管能正常工作,但是不是最優的方法呢?是不是可讀性好、易于理解、可維護佳或容易更新?當一段時間后再來review你的代碼,一定會發現很多需要改進的地方,需要重新組織代碼或刪掉多余的內容等等。這實際上就是在“整理”你的代碼了,可以很大程度提高你的代碼質量。但事情往往不是這樣,我們常常承受著高強度的工作壓力,根本沒有時間來整理代碼,因此通過代碼注釋寫文檔其實是不錯的機會。 往往在寫注釋文檔的時候,你會發現很多問題。你也會重新思考源代碼中不合理之處,比如,某個方法中的第三個參數比第二個參數更常用,第二個參數多數情況下取值為true,因此就需要對這個方法接口進行適當的改造和包裝。 寫出易讀的代碼(或API),是指別人能輕易讀懂程序的思路。所以你需要采用更好的思路來解決手頭的問題。 盡管我們認為“草稿”不甚完美,但至少也算“抱佛腳”的權宜之計,一眼看上去是有點“草”,不過也無所謂,特別是當你處理的是一個關鍵項目時(會有人命懸與此)。其實你應當扔掉你所給出的第一個解決方案,雖然它是可以正常工作的,但畢竟是一個草率的方案,不是最佳方案。你給出的第二個方案會更加靠譜,因為這時你對問題的理解更加透徹。第二個方案不是簡單的復制粘貼之前的代碼,也不能投機取巧尋找某種捷徑。 <a name="a29"></a> ## 相互評審 另外一種可以提高代碼質量的方法是組織相互評審。同行的評審很正式也很規范,即便是求助于特定的工具,也不失是一種開發生產線上值得提倡的步驟。但你可能覺得沒有時間去作代碼互審,沒關系,你可以讓坐在你旁邊的同事讀一下你的代碼,或者和她(譯注:注意是“她”而不是“他”)一起過一遍你的代碼。 同樣,當你在寫APIDoc或任何其他文檔的時候,同行的評審能幫助你的產出物更加清晰,因為你寫的文檔是讓別人讀的,你必須確保別人能理解你所作的東西。 同行的評審是一種非常不錯的習慣,不僅僅是因為它能讓代碼變得更好,更重要的,在評審的過程中,評審人和代碼作者通過分享和討論,兩人都能取長補短、相互促進。 如果你的團隊只有你一個開發人員,找不出第二個人能給你作代碼評審,這也沒關系。你可以通過將你的代碼片段開源,或把有意思的代碼片段貼在博客中,會有人對你的代碼感興趣的。 另外一個非常好的習慣是使用版本管理工具(CVS,SVN或Git),一旦有人修改并提交了代碼,都會發郵件通知組內成員。雖然大部分郵件都進入了垃圾箱,但總是會碰巧有人在工作間隙看到你所提交的代碼,并對代碼做出一些評價。 <a name="a30"></a> ## 生產環境中的代碼壓縮(Minify) 這里所說的代碼壓縮(Minify)是指去除JavaScript代碼中的空格、注釋以及其他不必要的部分,用以減少JavaScript文件的體積,降低網絡帶寬損耗。我們通常使用類似YUICompressor(Yahoo!)或Closure Compiler(Google)的壓縮工具來為網頁加載提速。對于生產環境(譯注:“生產環境”指的是項目上線后的正式環境)中的腳本是需要作壓縮的,壓縮后的文件體積能減少至原來的一半以下。 下面這段代碼是壓縮后的樣子(這段代碼是YUI2庫中的Event模塊): ``` YAHOO.util.CustomEvent=function(D,C,B,A){this.type=D;this.scope=C||window;this.silent =B;this.signature=A||YAHOO.util.CustomEvent.LIST;this.subscribers=[];if(!this.silent) {}var E="_YUICEOnSubscribe";if(D!==E){this.subscribeEvent=new YAHOO.util.CustomEvent(E,this,true);}... ``` 除了去除空格、空行和注釋之外,壓縮工具還能縮短命名的長度(前提是保證代碼的安全),比如這段代碼中的參數A、B、C、D。壓縮工具只會重命名局部變量,因為更改全局變量會破壞代碼的邏輯。這也是要盡量使用局部變量的原因。如果你使用的全局變量是對DOM節點的引用,而且程序中多次用到,最好將它賦值給一個局部變量,這樣能提高查找速度,代碼也會運行的更快,此外還能提高壓縮比、加快下載速度(譯注:在服務器開啟Gzip的情況下,對下載速度的影響幾乎可以忽略不計)。 補充說明一下,Goolge Closure Compiler還會對全局變量進行壓縮(在“高級”模式中),這是很危險的,且對編程規范的要求非常苛刻。它的好處是壓縮比非常高。 對生產環境的腳本做壓縮是相當重要的步驟,它能提升頁面性能,你應當使用工具來完成壓縮。千萬不要試圖手寫“壓縮好的”代碼,你應當堅持使用語義化的變量命名,并保留足夠的空格、縮進和注釋。你寫的代碼是需要被人閱讀的,所以應當將注意力放在代碼可讀性和可維護性上,代碼壓縮的工作交給工具去完成。 <a name="a31"></a> ## 運行 JSLint 在上一章我們已經介紹了JSLint,這里我們介紹更多的使用場景。對你的代碼進行JSLint檢查是非常好的編程習慣,你應該相信這一點。 JSLint的檢查點都有哪些呢?它會對本章討論過的一些模式(單var模式、parseInt()的第二個參數、總是使用花括號)做檢查。JSLint還包括其他方面的檢查: - 不可達代碼 - 在使用變量之前需要聲明 - 不安全的UTF字符 - 使用void、with、和eval - 無法正確解析的正則表達式 JSLint是基于JavaScript實現的(它是可以通過JSLint檢查的),它提供了在線工具,也可以下載使用,可以運行于很多種平臺的JavaScript解析器。你可以將源碼下載后在本地運行,支持的環境包括WSH(Windows Scripting Host,Windows)、JSC(JavaScriptCore,MacOSX)或Rhino(Mozilla開發的JavaScript引擎)。 可以將JSLint下載后和你的代碼編輯器配置在一起,著是一個不錯的注意,這樣每次你保存代碼的時候都會自動執行代碼檢查(比如配置快捷鍵)。 <a name="a32"></a> ## 小結 本章我們講解了編寫可維護性代碼的含義,本章的討論非常重要,它不僅關系著軟件項目的成功與否,還關系到參與項目的工程師的“精神健康”和“幸福指數”。隨后我們討論了一些最佳實踐和模式,它們包括: - 減少全局對象,最好每個應用只有一個全局對象 - 函數都使用單var模式來定義,這樣可以將所有的變量放在同一個地方聲明,同時可以避免“聲明提前”給程序邏輯帶來的影響。 - for循環、for-in循環、switch語句、“禁止使用eval()”、不要擴充內置原型 - 遵守統一的編碼規范(在任何必要的時候保持空格、縮進、花括號和分號)和命名約定(構造函數、普通函數和變量)。 本章還討論了其他一些和代碼本身無關的實踐,這些實踐和編碼過程緊密相關,包括書寫注釋、生成API文檔,組織代碼評審、不要試圖去手動了“壓縮”(minify)代碼而犧牲代碼可讀性、堅持使用JSLint來對代碼做檢查。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看