《編寫可維護的 JavaScript》讀書筆記

最近閱讀了《編寫可維護的 JavaScript》,在這裏記錄一下讀書筆記。書中主要基於三個方向來說解怎麼增長代碼的可維護性:編程風格編程實踐自動化javascript

筆記已加入到個人博客裏,歡迎 Star。html

編程風格

  • 縮進方式java

    • 使用空格(推薦)。
    • 使用製表符(Tab 鍵)。git

      • 不一樣的操做系統對於製表符的解釋不一致,致使看起來不一致。
  • 命名方式github

    • 變量名用名詞。編程

      • 常量使用大寫單詞加下劃線 MAX_NUM。
    • 函數名用動詞開頭數組

      • 構造函數用大駝峯寫法。
  • null 對象的用法瀏覽器

    • 初始化一個變量。
    • 和一個已經初始化的變量作比較。
    • 函數參數指望是對象時,用做參數傳入。
    • 函數返回值是對象時,能夠當作返回值。
// 好的寫法
var person = null;

function getPerson(num){

  if (num >5) {
    return new Person('Lily');
  } else {

    // 好的寫法
    return null;
  }
}

var person = new Person();

// 好的寫法
console.log(person === null);
  • 不要和非對象,或者未賦值變量作比較。
  • 不要用來檢測函數參數是否傳入。
var person;

// 很差的寫法
console.log(person === null);

function doSomeThing(arr1, arr2){

  // 很差的寫法
  if(arr1 === null){
    console.log('arr1 參數沒有傳遞');
  }
}

編寫註釋

使用註釋的通常原則是:讓代碼變得更清晰。咱們通常經過這幾點來註釋代碼:服務器

  • 難以理解的代碼。
  • 可能被誤解的代碼。
  • 瀏覽器 hack。

註釋的書寫規範:dom

  • 註釋的上方須要一個空行。
  • 單行註釋,若是在尾部註釋,須要加一個空格。
  • 多行註釋中的每一行需對齊,最好也有一個星號。
  • 能夠加一些註釋聲明。

    • TODO 代碼未完成。
    • HACK 代碼走了一個捷徑,應註明緣由
    • FIXME 代碼有問題,應儘快修復
    • REVIEW 代碼的改動須要評審
var person = null;

if (condition) {

  // 作一些事情
  doSomeThing(); // 作一些事情
}

/*
  * 這是建立一個對象
  * 第二行
*/
var p = new Person();


/**
@method merge
@param {Object} 被合併的對象
@return {Object} 一個新的對象
**/
function doSomeThing(obj) {
  return Object.assign(obj,{asd:123});
}

// REVIEW: 有更好的寫法嗎?
if(document.all){

}

語句和表達式

在語句和表達式一章,書中主要是寫關於大括號對齊方式等,如下兩點讓我影響特別深入:

  • 循環中儘量避免使用 continue,使用條件判斷替代(可讀性更好)。
  • for in 循環只是用來遍歷對象的,不要用來遍歷數組。若是不須要遍歷原型鏈上的對象,可使用 hasOwnProperty

變量、函數和運算符

  • 定義變量

    • 合併 var 語句,讓代碼更短。
    • 函數內部的變量定義應做爲函數內的第一條語句。
    • for 語句初始化部分不該該有變量聲明,應提早到外面。
  • 當即執行的函數須要被一眼看出來,能夠將函數用一堆圓括號包裹起來。
  • 嚴格模式不要加在全局,只應加在須要用到的函數做用域內,避免影響其餘庫。
  • 禁止使用 eval,new Function,setTimeOut,setInterval 的字符串形式。
  • 三元運算符僅僅用在賦值語句上,不要成爲 if else 的替代品。
提示
String 類型能夠調用 String 包裝器的方法,是由於語句的背後,JS 引擎建立了 String 類型的新實例,緊跟着就銷燬了,因此給 String 類型上添加屬性和方法是沒有用的。

編程實踐

在 WEB 開發中,用戶界面是由三個彼此隔離又相互做用的層定義的:HTML、CSS、JavaScript。因爲這三個部分是相互做用的,應該儘可能分開這幾個模塊,以加強代碼的可維護性。

  • 將 JS 從 CSS 中抽離出來。

    • 避免使用 CSS 表達式。
  • 將 CSS 從 JS 中抽離出來。

    • 避免使用 JS 直接操做 dom.style 屬性。
    • 應該只須要操做 CSS 的 className 屬性。
  • 將 JS 從 HTML 中抽離出來。

    • 避免使用 DOM0 級事件處理函數。 例如:<div onclick="xxx"></div>
    • 使用自定義事件處理函數。
  • 將 HTML 從 JS 中抽離出來。

    • 避免 JS 使用寫死的 HTML 字符串,去 innerHTML 插入代碼。
div.innerHTML = '<h1>你好</h1>';

JS 可使用一下幾種方式來操做 HTML:

  • 請求服務器,返回一段 HTML 文本,而後 innerHTML。
  • 簡單客戶端模板,寫一個頁面不可見的模板,用變量的值去填充模板後,插入到頁面中來。
  • 複雜客戶端模板,使用 Handlebars,建立模板,渲染數據。

避免建立全局變量

咱們都知道,在全局做用域下定義的變量和方法,都會掛載到 window 對象上,隨着對象越掛越多,項目也就愈來愈難以維護了。建立全局變量還會致使如下幾個問題:

  • 命名衝突
  • 代碼脆弱性

    • 若是大量函數都引用了全局變量,而一旦某個地方不當心改變了值,全部使用的地方就完了。
  • 難以測試

    • 開發環境,測試環境,線上環境,都須要引入一套不一樣的全局變量,不方便維護。

解決全局變量方案

  • 單全局變量

    • 只使用一個對象定義在全局變量上,其餘的對象都做爲這個對象的屬性和方法。
    • 引用命名空間概念:

      • 調用 namespace() 來聲明開發者將要使用的命名空間,即:將對象掛載到哪一個對象下,若是不存在,就建立一個對象。
  • AMD 模塊化

    • 定義一個 define 方法,將模塊名,依賴,都傳進去,在工廠方法裏就能使用所依賴的模塊了。
// AMD 模塊定義
define('module-name',['dep1','dep2'],function(dep1,dep2){});
  • 使用 RequireJS 更好地引入模塊。

RequireJS 增長了另外一個全局函數 require(),專門用來加載指定的依賴和執行回調函數。

require(['my-book'], function(books){
  console.log(books);
});
  • 零全局變量

    • 若是你的腳本很是短,而且不須要和其餘代碼產生交互,能夠使用一個自執行函數來實現模塊化

事件處理

在編寫事件處理程序時,咱們應該遵照一下幾點:

  • 隔離應用邏輯。將應用邏輯從全部的事件處理程序中抽離出來。
  • 不要分發事件對象。只傳遞須要的數據,給應用邏輯就好了。
  • 讓事件處理程序成爲接觸到 Event 對象的惟一函數。其餘須要用到的,就從事件處理程序中傳遞。

實際應用場景是這樣的:

<button onclick="doSomeThing()">click me</button>

<script>
// 事件處理程序
function doSomeThing(e) {
  var clientX = e.clientX;

  // 不要將e 傳入應用邏輯中,只傳遞須要使用的字段
  log(clientX);
}

// 應用邏輯
function log(text){
  console.log(text);
}
</script>

類型檢測

檢測原始類型

推薦使用 typeof 來檢測原始類型的值。typeof 自己是一個表達式,因此推薦使用無括號的寫法。

檢測自定義類

JS 檢測自定義類時,最好的作法是使用 instanceof,這也是惟一的方法。

function Person(){}

var p = new Person():

console.log(p instanceof Person);

instanceof 也能夠檢測引用對象的值。但可能會檢測到原型對象上。

var now = new Date();

console.log(now instanceof Date); // true
console.log(now instanceof Object); // true

檢測函數

typeof 是檢測 JS 檢測函數的最佳選擇。但 IE8 及其之前的瀏覽器對 DOM 上的方法實現方式有問題。

// IE 8 會存在問題
typeof document.getElementById    // object

檢測數組

使用 Object.prototype.toString.call(value) === '[object Array]' 檢測數組效果很不錯,在 ES6 中能夠經過 Array.isArray 來實現。

檢測屬性

檢測屬性使用如下兩個操做符:

  • in 運算符(會檢測原型鏈上的屬性)
  • hasOwnProperty

將配置數據從代碼中分離出來

在實際的項目中能夠遇到這種問題,原本只須要更改一些靜態數據,但因爲不當心改到了其餘業務代碼,致使部分業務代碼報錯。因此,把須要配置的靜態數據抽離出來是很是利於維護的。通常按照如下幾點將數據抽離出來:

  • URL
  • 展現給用戶的字符串
  • 重複的值
  • 設置信息
  • 任何可能發生變動的值

咱們能夠將抽離出來的配置數據放在如下幾個地方:

  • 抽離數據到一個 JSON 文件中。(經過請求去獲取)
  • 抽離封裝到一個 JSONP 文件中。(經過 script 標籤獲取)
  • 使用純 javascript。(直接讀取)

拋出自定義錯誤

咱們在開發項目時,遇到錯誤其實並不可怕,可怕的將這些錯誤帶到線上環境中去。爲此,在程序的一些關鍵地方,使用自定義錯誤,可讓咱們在上線前提早發現問題,避免出錯的影響變大。編寫自定義錯誤能夠遵照一下幾點:

  • 老是在錯誤信息中包含函數名稱,以及函數失敗的緣由。
  • 只在該拋出錯誤的地方拋出錯誤,沒必要要過分的進行錯誤預判,拋出錯誤。
  • 若是使用 try catch ,catch 裏不能爲空,應該對錯誤作一些處理。
  • 對於自定義錯誤,最好是繼承 Error 錯誤類型,瀏覽器會給 Error 對象附加一些額外的信息。
function MyError(message){
  this.message = message;
}
MyError.prototype = new Error();

不是你的對象不要動

在咱們編寫代碼的時候,會用到不少其餘對象,一些是存在於上下文做用域中、一些是存在於其餘庫裏等等,這些對象都不是咱們本身定義的。對於這些對象,咱們要作到徹底不去改動。其餘對象主要包括:

  • JS 原生對象
  • DOM 對象
  • 瀏覽器對象模型,BOM 對象
  • 類庫的對象

即便是目前項目中的對象,只要不是你寫的,也不該該隨便修改。

  • 不要覆蓋方法
  • 不新增方法

    • 例如:Prototype 庫,是在源生 JS 上擴展一些方法,但擴展的方法可能之後被新的規範所使用,Prototype 庫不得不當即修改以支持新的規範。
  • 不刪除方法

若是是必需要修改其餘對象,能夠經過繼承的方式來克隆一個以前的對象,而後再進行擴展。

  • 基於對象的繼承(常規的繼承)
  • 基於類型的繼承(如繼承 Error 類型)

關於 Polyfill

Polyfills 的優勢是:若是瀏覽器提供原生實現,能夠很是輕鬆的移除他們。若是瀏覽器沒有實現,就使用現有的方法,巧妙地實現,實現過程可能會不精確。

爲了防止對象修改

  • 防止擴展 Object.preventExtensions() 不可擴展
  • 密封對象 Object.seal() 不能刪除已存在屬性,不可擴展
  • 凍結對象 Object.freeze() 不能修改已存在的屬性,不能刪除已存在屬性,不可擴展

瀏覽器嗅探

一、user-agent 方式(能夠被篡改)。

二、使用特性檢測。爲特定瀏覽器的特性進行測試,並僅當特性存在時便可應用特性檢測。例如:

// 早期瀏覽器不支持 getElementById
if(document.getElementById){
  var dom = document.getElementById('xx')
} else if(document.all){
  var dom = document.all('xx')
}

特性檢測的流程:

  • 探測標準的方法。
  • 探測不一樣瀏覽器的特定方法。
  • 都不存在時,提供一個合乎邏輯的備用方法。

避免特性推斷

推斷是假設並不是事實。例如:這裏根據 getElementsByTagName 去推斷 getElementById,顯然是不合理的。

if(document.getElementsByTagName){
  var dom = document.getElementById('xx')
}

避免瀏覽器推斷

經過 document.all 判斷就是 IE 瀏覽器了,這是「自做聰明」的,由於其餘瀏覽器也可能存在 document.all。

if(document.all){
  console.log('This is IE');
}

自動化

如下列舉了書中介紹的自動化配置過程,很實用,如今的項目均可以使用這些思想來配置持續集成。惋惜的是,如今看來書中的信息比較滯後(由於寫的早),不少介紹的庫已通過時了,這裏就不詳細介紹了。

  • ant 構建工具
  • 校驗語法錯誤
  • 文件合併和加工
  • 文件精簡和壓縮
  • 文檔化
  • 自動化測試

    • PhantomJS
  • 組裝到一塊兒

    • jekins

總結

這本書看着很快,三天時間就看完了,但書中的編碼規範,編程實踐部分仍是學到了很多東西。如今寫代碼會經常冒出幾個問題:註釋該怎麼寫?類型判斷要怎麼才最好?數據代碼是否抽離?

相關文章
相關標籤/搜索