CodingWife 寇丁人妻日安

【讀書筆記】JavaScript Design Pattern Chapter02 精要

2018-11-07

撰寫可維護的程式碼

容易維護的程式碼必須具備下列條件:

  • 可讀性
  • 一致性
  • 可預料的
  • 看起來像同個人寫的
  • 文件化

減少全域變數

JavaScript 使用函數來管理作用域。

  • 區域變數:在函式內的變數對於該函數來說為區域變數。
  • 全域變數:定義在函式作用域之外,或者沒被宣告就直接使用的變數。

每種 JavaScript 執行環境都有全域物件,在函式作用域之外用this就可以存取得到,所有建立的全域變數都會成為全域物件的屬性。

全域變數的問題

因為都存在在相同的全域命名空間,會有命名衝突的問題,之後會學習到命名空間模式,和自我執行的立即執行函式。

不小心建立全域變數的兩個狀況

  • 未宣告就使用變數,會自動為全域物件的屬性。
  • 連續賦值(var a = b = 0;,a 為區域變數,b 為全域變數)。

遺漏 var 的副作用

明確定義的全域變數和隱含的全域變數有個微小差別,就是是否能用delete運算子將變數刪除。

  • 用 var 創造出的全域變數不可刪除
  • 不使用 var 隱含創造出的全域變數可以刪除

意思是說隱含創造出的全域變數並不是真的變數,而是全域物件的屬性,屬性可以透過delete運算子刪除,但是變數不行。

存取全域物件

在瀏覽器上,任何程式碼都可以透過 window 屬性存取全域物件。

單一 var 模式

在函式最開頭使用單一 var 述句是個非常有用的模式:

  • 再找所有函式所需的變數,只需找一個地方就好。
  • 避免未宣告就使用變數所造成的邏輯錯誤。
  • 盡少使用全域變數
  • 較少的程式碼

用單一 var 述句宣告多個變數,並用逗號隔開,同時也賦予初始值,可避免邏輯錯誤。

Hosting:分散的 var 所製造的問題

JavaScript 允許一個函式內有多個 var 述句,並放在任何位置,他們的行為跟在函式頂端宣告一樣,此行為叫做 Hosting(提升)。

程式碼會進行兩個處理:

  • 第一階段:語法分析和讀取內容,將產生變數、函數宣告,以及函式參數。
  • 第二階段:執行時期,將產生函式表達式和不合格識別字。但為特殊目的,有時我們仍可以採用 hosting 概念。

for 迴圈

使用迴圈可以重複整個陣列或類似陣列的物件,如果可以最好是預先計算陣列的長度。

遵照單一 var 模式,可以將 var 提出迴圈外:

function looper(){
var i=0,
max,
myarray=[];
for(i=0,max=myarray.length;i<max;i++){
...
}
}

優點:一致性
缺點:重構時,若要將迴圈複製,要確認有把imax複製過去。
調整:將i++替換為i=i+1i+=1
(JSLint 對++--提示「過度使用伎倆」)

for-in 迴圈

for-in應該使用在重複非陣列物件,又稱為列舉,而陣列應該使用for迴圈。

在列舉屬性很重要的是使用hasOwnProperty()方法來過濾掉原型鍊的屬性。

var man = {
hands: 2,
legs: 2,
head: 1
}
if(typeof Object.prototype.clone === 'undefined'){
Object.prototype.clone = function (){ console.log('hi')}
}

for (var i in man){

if(man.hasOwnProperty(i)){
console.log(i,':',man[i])
}
}
// hands : 2
// legs : 2
// head : 1
//
for (var i in man){
console.log(i,':',man[i])
}
// hands : 2
// legs : 2
// head : 1
// clone : ƒ (){ console.log('hi')}

意思就是假設你有自訂prototype,使用for-in也會將prototype的屬性列舉出來,不使用檢查也沒問題,只要你可以預期你的程式碼,但如果你不確定的話,最好還是加上去。

另一種過濾方法

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

不要擴充內建型別的原型

如果擴充Object()Array(),或是Function(),可能會讓程式碼難以預測。

以下可破例的狀況:

  • 可預期 ECMA 將你新增的方法實作為未來瀏覽器可能有用的功能
  • 你確定你的方法在原生的 prototype 不存在,也沒有在其他程式碼中被實作過。
  • 你已經清楚與你的團隊溝通這個變更並文件化

switch 模式

  • 將每個 case 與 switch 並排(這是大括號規則的例外)
  • 將每個 case 內的程式碼縮排
  • 用清楚的break;結束每個 case
  • 避免未完結的 case (意思就是特別刪除break),如果堅決如此,應該寫文件說明
  • 用一個default:結束 switch ,以確保都有合理的結果

避免隱含的型別轉換

JavaScript 在比較變數時會做隱含的型別轉換,例如:false == 0"" == 0皆會回傳 true 。

建議皆使用 ===!== 以維持程式碼的一致性,也可以減少一些精神閱讀。

避免使用 eval()

eval 會將傳入的字串當作程式碼執行,同樣傳遞字串給 setInterval()setTimeOut()Function()也是如此。

為了避免安全性的問題,應避免使用 eval()

new Function() 建構式和 eval() 非常接近,假設真的非用 eval() 那不如使用 new Function() 建構式取代,優點是在函式作用域中執行,只要使用 var 宣告的變數都不會自動變成全域變數,而使用 eval() 則是在全域的環境執行。

另外注意:Functionnew Function() 是不同的。

使用 parseInt() 轉型成數值

使用 parseInt() 將一個字串轉換成數值,第二個參數是轉型用的基數,第二個參數通常會被忽略,然而不該如此。
如果當要轉換的字串開頭為 0 時,而又忽略第二個參數,基數可能為 108 ,具體狀況則要根據瀏覽器環境而決定。

將字串轉為數值也可使用Number()

編碼規範

縮排

不統一的縮排比沒縮排更糟糕。

大括號

iffor 的單行敘述,不加大括號可以執行,但應該加上大括號,以避免新增新的一行導致錯誤。

左括號的位置

有些狀況下會因為括號的位置造成程式執行結果不同,影響原因是因為分號插入機制,如果在行尾遺漏分號,JavaScript 會自動補齊,但應該永遠在行尾加入分號,避免 JavaScript 隱晦的補上,而導致程式碼有含糊之處。

空格

建議使用空格的地方:

  • 區隔 for 迴圈各部分分號之後
  • for 迴圈,初始化多個變數之間
  • 區隔 array 物件的逗號之後
  • 物件屬性間的逗號之後,以及區隔 key/value 的分號之後
  • 區隔參數的逗號之後
  • 區隔函式宣告的左括號之前
  • 匿名函式的 function 之後
  • 區隔所有運算元及運算子
  • 在左大括號({)之前

命名慣例

讓建構式字母為大寫

將建構式命名為大寫,可快速辨別是建構式還是普通函式。

字詞的分隔方式

針對建構式可用大駝峰寫法,一般函式使用小駝峰寫法,非函式的變數也可以用全小寫,中間底線區隔。

其他命名模式

在整個程式的生命週期都不會變動的常數,可採用全大寫的命名方式。
還有一種 private 成員命名慣例是在屬性和方法前面加上底線,以表示為 private 。

下方為其他 _private 規範的變化:

  • 在變數尾巴加上底線表示。
  • 用一個底線的前綴作為 _protected 屬性、用兩個底線作為 __private 屬性。
  • Firefox 有一些內部屬性命名前後都會加上兩個底線。

撰寫註解

通常要為每個函式的參數、回傳值以及不常見的技術編寫註解。假設你有五到六行的程式碼來執行一個工作,用一行簡單敘述程式碼的目的為何寫在此,之後閱讀程式碼的人就不用詳讀細節。

持續更新註解,過時的註解比沒註解更糟

撰寫 API 文件

推薦工具:

  • JSDoc Toolkit
  • YUIDoc

API 文件生成

  • 發布工具產生的結果(通常是 HTML 頁面)
  • 撰寫特別格式的程式碼
  • 執行工具來分析程式碼和註解

練習寫註解區塊:

/**
* 反轉一個字串
*
* @param {String} 輸入字串來做反轉
* @return {String} 反轉後的字串
*/

var reverse = function (input){
// ...
return output;
}