1. 程式人生 > >湯姆大叔的部落格 -【1】編寫高質量JavaScript程式碼的基本要點

湯姆大叔的部落格 -【1】編寫高質量JavaScript程式碼的基本要點

1、最小全域性變數(Minimizing Globals)

(1)JavaScript通過函式管理作用域。在函式內部宣告的變數只在這個函式內部,函式外面不可用。
(2)全域性變數就是在任何函式外面宣告的或是未宣告直接簡單使用的。
(3)每個JavaScript環境有一個全域性物件,當你在任意的函式外面使用this的時候可以訪問到。你建立的每一個全域性變數都成了這個全域性物件的屬性。在瀏覽器中,方便起見,該全域性物件有個附加屬性叫做window,此window(通常)指向該全域性物件本身。

這裡寫圖片描述
全域性變數的問題
全域性變數的問題在於,你的JavaScript應用程式web頁面

上的所有程式碼都共享了這些全域性變數,他們住在同一個全域性名稱空間,所以當程式的兩個不同部分定義同名但不同作用的全域性變數的時候,命名衝突在所難免。

web頁面包含不是該頁面開發者所寫的程式碼也是比較常見的,例如:

第三方的JavaScript庫
廣告方的指令碼程式碼
第三方使用者跟蹤和分析指令碼程式碼
不同型別的小元件,標誌和按鈕

比方說,該第三方指令碼定義了一個全域性變數,叫做result;接著,在你的函式中也定義一個名為result的全域性變數。其結果就是後面的變數覆蓋前面的,第三方指令碼就一下子嗝屁啦!

因此,要想和其他指令碼成為好鄰居的話,儘可能少的使用全域性變數是很重要的。

隱含的全域性變數
—不用宣告

```不需要宣告就可以使用變數```
```JavaScript有隱含的全域性概念,意味著你不宣告的任何變數都會成為一個全域性物件屬性。```

這裡寫圖片描述
另一個建立隱式全域性變數的反例就是使用任務鏈進行部分var宣告
這裡寫圖片描述

正確的寫法
這裡寫圖片描述

然而,另外一個避免全域性變數的原因是可移植性。如果你想你的程式碼在不同的環境下(主機下)執行,使用全域性變數如履薄冰,因為你會無意中覆蓋你最初環境下不存在的主機物件(所以你原以為名稱可以放心大膽地使用,實際上對於有些情況並不適用)。

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

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

(1)通過var建立的全域性變數(任何函式之外的程式中建立)是不能被刪除的。
(2)無var建立的隱式全域性變數(無視是否在函式中建立)是能被刪除的。

這表明,在技術上,隱式全域性變數並不是真正的全域性變數,但它們是全域性物件的屬性。屬性是可以通過delete操作符刪除的,而變數是不能的:
表明隱式全域性變數,只是全域性物件的屬性,可以被刪除,變數是不可被刪除的
這裡寫圖片描述
在ES5嚴格模式下,未宣告的變數(如在前面的程式碼片段中的兩個反面教材)工作時會丟擲一個錯誤。

3、訪問全域性物件(Access to the Global Object)

在瀏覽器中,全域性物件可以通過window屬性在程式碼的任何位置訪問(除非你做了些比較出格的事情,像是聲明瞭一個名為window的區域性變數)。但是在其他環境下,這個方便的屬性可能被叫做其他什麼東西(甚至在程式中不可用)。如果你需要在沒有硬編碼的window識別符號下訪問全域性物件,你可以在任何層級的函式作用域中做如下操作:
這裡寫圖片描述

4、單var形式(Single var Pattern)

在函式頂部使用單var語句是比較有用的一種形式,其好處在於:

提供了一個單一的地方去尋找功能所需要的所有區域性變數
防止變數在定義之前使用的邏輯錯誤
幫助你記住宣告的全域性變數,因此較少了全域性變數//zxx:此處我自己是有點暈乎的…
少程式碼(型別啊傳值啊單線完成)

單var形式長得就像下面這個樣子:
這裡寫圖片描述
您可以使用一個var語句宣告多個變數,並以逗號分隔。像這種初始化變數同時初始化值的做法是很好的。這樣子可以防止邏輯錯誤(所有未初始化但宣告的變數的初始值是undefined)和增加程式碼的可讀性。在你看到程式碼後,你可以根據初始化的值知道這些變數大致的用途,例如是要當作物件呢還是當作整數來使。

你也可以在宣告的時候做一些實際的工作,例如前面程式碼中的sum = a + b這個情況,另外一個例子就是當你使用DOM(文件物件模型)引用時,你可以使用單一的var把DOM引用一起指定為區域性變數,就如下面程式碼所示的:
這裡寫圖片描述

5、預解析:var散佈的問題(Hoisting: A Problem with Scattered vars)

JavaScript中,你可以在函式的任何位置宣告多個var語句,並且它們就好像是在函式頂部宣告一樣發揮作用,這種行為稱為 hoisting(懸置/置頂解析/預解析)。對於JavaScript,只 要你的變數是在同一個作用域中(同一函式),它都被當做是宣告的,即使是它在var宣告前使用的時候。
這裡寫圖片描述

6、for迴圈(for Loops)

這裡寫圖片描述
這種形式的迴圈的不足在於每次迴圈的時候陣列的長度都要去獲取下。這回降低你的程式碼,尤其當myarray不是陣列,而是一個HTMLCollection物件的時候。

HTMLCollections指的是DOM方法返回的物件
這裡寫圖片描述
還有其他一些HTMLCollections,這些是在DOM標準之前引進並且現在還在使用的。有:
這裡寫圖片描述

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

這就是為什麼當你迴圈獲取值時,快取陣列(或集合)的長度是比較好的形式,正如下面程式碼顯示的:

這裡寫圖片描述
這樣,在這個迴圈過程中,你只檢索了一次長度值。
這種形式具有一致性的好處,因為你堅持了單一var形式。不足在於當重構程式碼的時候,複製和貼上整個迴圈有點困難。例如,你從一個函式複製了一個迴圈到另一個函式,你不得不去確定你能夠把i和max引入新的函式(如果在這裡沒有用的話,很有可能你要從原函式中把它們刪掉)。

最後一個需要對迴圈進行調整的是使用下面表示式之一來替換i++。
這裡寫圖片描述

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

少了一個變數(無max)
向下數到0,通常更快,因為和0做比較要比和陣列長度或是其他不是0的東西作比較更有效率
這裡寫圖片描述

7、for-in迴圈(for-in Loops)

for-in迴圈應該用在非陣列物件的遍歷上,使用for-in進行迴圈也被稱為“列舉”。

從技術上將,你可以使用for-in迴圈陣列(因為JavaScript中陣列也是物件),但這是不推薦的。因為如果陣列物件已被自定義的功能增強,就可能發生邏輯錯誤。另外,在for-in中,屬性列表的順序(序列)是不能保證的。所以最好陣列使用正常的for迴圈物件使用for-in迴圈

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

這裡寫圖片描述
在這個例子中,我們有一個使用物件字面量定義的名叫man的物件。在man定義完成後的某個地方,在物件原型上增加了一個很有用的名叫 clone()的方法。
此原型鏈是實時的,這就意味著所有的物件自動可以訪問新的方法。為了避免列舉man的時候出現clone()方法,你需要應用hasOwnProperty()方法過濾原型屬性。如果不做過濾,會導致clone()函式顯示出來,在大多數情況下這是不希望出現的。
這裡寫圖片描述

另外一種使用hasOwnProperty()的形式是取消Object.prototype上的方法。像是:
這裡寫圖片描述
這裡寫圖片描述

8、(不)擴充套件內建原型((Not) Augmenting Built-in Prototypes)

擴增建構函式的prototype屬性是個很強大的增加功能的方法,但有時候它太強大了。

增加內建的建構函式原型(如Object(), Array(), 或Function())挺誘人的,但是這嚴重降低了可維護性,因為它讓你的程式碼變得難以預測。使用你程式碼的其他開發人員很可能更期望使用內建的 JavaScript方法來持續不斷地工作,而不是你另加的方法。

另外,屬性新增到原型中,可能會導致不使用hasOwnProperty屬性時在迴圈中顯示出來,這會造成混亂。

因此,不增加內建原型是最好的。你可以指定一個規則,僅當下面的條件均滿足時例外:

可以預期將來的ECMAScript版本或是JavaScript實現將一直將此功能當作內建方法來實現。例如,你可以新增ECMAScript 5中描述的方法,一直到各個瀏覽器都迎頭趕上。這種情況下,你只是提前定義了有用的方法。
如果您檢查您的自定義屬性或方法已不存在——也許已經在程式碼的其他地方實現或已經是你支援的瀏覽器JavaScript引擎部分。
你清楚地文件記錄並和團隊交流了變化。
如果這三個條件得到滿足,你可以給原型進行自定義的新增,形式如下:
這裡寫圖片描述

9、switch模式(switch Pattern)

這裡寫圖片描述

10、避免隱式型別轉換(Avoiding Implied Typecasting )

這裡寫圖片描述

11避免(Avoiding) eval()

eval() 函式可計算某個字串,並執行其中的的 JavaScript 程式碼。

使用eval()也帶來了安全隱患,因為被執行的程式碼(例如從網路來)可能已被篡改。
這裡寫圖片描述

同樣重要的是要記住,給setInterval(), setTimeout()和Function()建構函式傳遞字串,大部分情況下,與使用eval()是類似的,因此要避免。在幕後,JavaScript仍需要評估和執行你給程式傳遞的字串:

這裡寫圖片描述
使用新的Function()構造就類似於eval(),應小心接近。這可能是一個強大的構造,但往往被誤用。如果你絕對必須使用eval(),你 可以考慮使用new Function()代替。有一個小的潛在好處,因為在新Function()中作程式碼評估是在區域性函式作用域中執行,所以程式碼中任何被評估的通過var 定義的變數都不會自動變成全域性變數。

另一種方法來阻止自動全域性變數是封裝eval()呼叫到一個即時函式中。
這裡寫圖片描述

另一間eval()和Function構造不同的是eval()可以干擾作用域鏈,而Function()更安分守己些。管你在哪裡執行 Function(),它只看到全域性作用域。所以其能很好的避免本地變數汙染。在下面這個例子中,eval()可以訪問和修改它外部作用域中的變數,這是 Function做不來的(注意到使用Function和new Function是相同的)。
這裡寫圖片描述

12、parseInt()下的數值轉換(Number Conversions with parseInt())

定義和用法
parseInt() 函式可解析一個字串,並返回一個整數。
(1)當字串以”0″開頭的時候就有可能會出問題
(2)部分時間進入表單域,在ECMAScript 3中,開頭為”0″的字串被當做8進位制處理了,但這已在ECMAScript 5中改變了。為了避免矛盾和意外的結果,總是指定基數引數。

這裡寫圖片描述
此例中,如果你忽略了基數引數,如parseInt(year),返回的值將是0,因為“09”被當做8進位制(好比執行 parseInt( year, 8 )),而09在8進制中不是個有效數字。

替換方法是將字串轉換成數字,包括:
這裡寫圖片描述