JS編程常識


一.UI層的松耦合

松耦合就是要求各層遵循“最少知識原則”,或者說是各層各司其職,不要越權:

  • HTML:結構層

  • CSS:表現層

  • JS:行為層

對於各層的職能,有一句比較貼切的解釋:HTML是名詞(n),CSS是形容詞(adj)和副詞(adv),JS是動詞

因為三層聯系緊密,實際應用中很容易越權:

耦合

1.從css中分離js

盡量不要用css表達式,如果非要用也應該把相應的代碼放在hack中,便於維護

2.從js中分離css

不要用ele.style.attr及ele.cssText,應該用操作類名代替

3.從html中分離js

不要用onclick等屬性直接指定事件處理函數,應該用添加事件處理器方式代替

一般不要用<script>標簽直接嵌入js代碼,盡量放在外部js文件中。當然,對於功能單一且不需要復用的代碼可以直接嵌在HTML中,例如表單驗證代碼

4.從js中分離html

不要直接用innerHTML硬編碼,可以用以下3種方式代替:

  1. 用Ajax從服務端獲取HTML串,避免硬編碼

  2. 用簡單的客戶端模版,有2種實現方式:

    • 用注釋攜帶模式串

    • 用script標簽攜帶模式串,把type屬性設置為瀏覽器無法識別的值,還應該給script標簽設置id屬性以便於獲取模式串,例如:

      <script type="text/x-my-template" id="list-item">
          <li><a href="%s">%s</li>
      </script>

      推薦使用這種方式,因為更容易獲取模式串

  3. 用復雜的客戶端模版,比如jade、ejs

P.S.因為html與css的解耦與js編程沒有關系,所以書中也沒有相應內容

二.少用全局變量

1.全局變量帶來的麻煩

  • 命名沖突

  • 代碼不健壯,函數需要的所有外部數據都應該用參數傳進來,而不要用全局變量傳參

  • 難以測試,需要重建整個全局環境

2.隱式全局變量

不要用隱式全局變量方式聲明全局變量,建議所有變量聲明都帶上var關鍵字

為了避免隱式全局變量,還應該開啟嚴格模式(”use strict”;),[IE10+]支持

3.單全局變量方式

  1. 用命名空間,提供一個避免命名空間沖突的方法:

    // 頂級命名空間
    var app = {
        /*
         * 創建/獲取子命名空間,支持鏈式調用
         */
        namespace: function(ns) {
            var parts = ns.split("."),
                object = this,
                i, len;
    
            for (i = 0, len = parts.length; i < len; i++) {
                if (!object[parts[i]]) {
                    object[parts[i]] = {};
                }
                object = object[parts[i]];
            }
    
            return object;  // 支持鏈式調用
        }
    }
    
    // 測試
    app.namespace("Consts").homepage = "http://ayqy.net/";
    app.namespace("Consts").author = "ayqy";
    // http://ayqy.net/, ayqy
    alert(app.Consts.homepage + ", " + app.Consts.author);
  2. 模塊化

    AMD/CMD,一點擴展知識如下:

    CommonJS是一套理論規范(比如js的理論規范是ES),而SeaJS, RequireJS都是對CommonJS的Modules部分的具體實現

    CommonJS是面向瀏覽器外(server端)的js制定的,所以是同步模塊加載,SeaJS是CommonJS的一個實現,而RequireJS雖然也是對CommonJS的一個實現,但它是異步模塊加載,算是更貼近瀏覽器的單線程環境

    總結:CommonJS的Modules部分提出了模塊化代碼管理的理論,為了讓js可以模塊化加載,而RequireJS, SeaJS等各種實現可以稱為模塊化腳本加載器

    • CMD:Common模塊定義,例如SeaJS

    • AMD:異步模塊定義,例如RequireJS

    都是用來定義代碼模塊的一套規范,便於模塊化加載腳本,提高響應速度

    CMD與AMD的區別:

  • CMD依賴就近。便於使用,在模塊內部可以隨用隨取,不需要提前聲明依賴項,所以性能方面存在些許降低(需要遍歷整個模塊尋找依賴項目)

  • AMD依賴前置。必須嚴格聲明依賴項,對於邏輯內部的依賴項(軟依賴),以異步加載,回調處理的方式解決

4.零全局變量方式

用IIFE(匿名函數立即執行)實現,針對不需要復用的功能模塊可以用IIFE完全消除全局變量,所以一般IIFE都是用來輔助命名空間/模塊化方式的

三.事件處理

1.典型用法(不好)

// 事件處理器
function handleClick(event) {
    var popup = document.getElementById("popup");
    popup.style.left = event.clientX + "px";
    popup.style.top = event.clientY + "px";
    popup.className = "display";
}

// 添加事件處理器
ele.addEventListener("click", handleClick);

2.分離應用邏輯(稍好一點)

var app = {
    // 事件處理
    handleClick: function(event) {
        this.showPopup(event);
    },

    // 應用邏輯
    showPopup: function(event) {
        var popup = document.getElementById("popup");
        popup.style.left = event.clientX + "px";
        popup.style.top = event.clientY + "px";
        popup.className = "display";
    }
};

// 添加事件處理器
// P.S.事件處理器是一個方法聲明,而不是方法調用,無法傳參,所以需要多一層匿名函數
ele.addEventListener("click", function() {
    app.handleClick(event);
});

3.不要傳遞事件對象(最好)

var app = {
    // 事件處理
    handleClick: function(event) {
        this.showPopup(event.clientX, event.clientY);   // 參數變了
    },

    // 應用邏輯
    showPopup: function(x, y) { // 形參變了
        var popup = document.getElementById("popup");
        popup.style.left = x + "px";
        popup.style.top = x + "px";
        popup.className = "display";
    }
};

// 添加事件處理器
ele.addEventListener("click", function() {
    app.handleClick(event);
});

不要傳遞事件對象”是一條優化原則,在js高程的優化部分也有提到過,但本書給出了詳細理由

直接傳遞事件對象存在以下缺點:

  1. 接口定義不明確,參數作用未知

  2. 難以測試(重建一個event對象?)

四.少與null比較

1.檢測基本值

用typeof檢測,但要注意typeof null返回object,這不太科學,因為js認為null是一個空對象的引用

但用 === null檢測DOM元素是合理的,因為null是document.getXXByXXX的可能輸出之一

2.檢測引用值

instanceof並不能准確地檢測子類型,而且不要用它檢測fun和arr,因為不能跨frame

  1. 檢測fun

用typeof檢測一般方法;用in檢測DOM方法

  1. 檢測arr

Object.prototype.toString.call(arr) === "[Object Array]"檢測

注意:ES5有原生的Array.isArray()方法,[IE9+]支持

3.檢測屬性

用in配合hasOwnProperty()檢測

注意:[IE8-]的DOM元素不支持hasOwnProperty(),用的時候要先檢測

五.分離配置數據

1.配置數據有哪些?

  1. 硬編碼的值

  2. 將來可能會變的值

比如:

  • URL

  • 需要顯示給用戶的字符串

  • 重復使用的唯一值

  • 設置(例如每頁顯示多少列表項)

  • 可能會變的任何值(不好維護的東西都算配置數據)

2.分離配置數據

先從應用邏輯中分離出配置數據,最簡單的可以把所有配置數據分級存放在自定義的config對象中

3.存儲配置數據

可以用js文件存儲配置數據,便於加載,但配置數據要嚴格符合js語法,容易出錯。作者建議把配置數據存儲為格式簡單的屬性文件再用工具轉換為JSON/JSONP/js格式文件用於加載,作者推薦一個自己寫的工具props2js

六.拋出自定義錯誤

1.Error的本質

用來標志出乎意料的東西,避免靜默失敗,便於調試

2.用js拋出錯誤

不要拋其它類型,要拋Error對象,因為兼容性,例如:

throw "error: invalid args";    // 有些瀏覽器不顯示該字符串

3.拋出錯誤的優點

精確定位錯誤,建議錯誤信息格式:函數名 + 錯誤原因

4.何時該拋出錯誤

只拋常用方法(工具方法)中的錯誤,一般原則:

  1. 修復了奇葩bug之后,應該添幾個自定義錯誤,防止錯誤再次發生

  2. 如果寫代碼的時候覺得某些點可能引起大麻煩,就應該拋出自定義錯誤

  3. 如果代碼是寫給別人用的,應該想想別人使用的時候可能遇到哪些問題,應該在自定義錯誤中給出提示

5.try-catch語句

finally不常用,因為會影響catch中的return

不要留空的catch塊,靜默失敗可能會把問題變得更麻煩

6.幾種錯誤類型

可以拋出原生的幾種錯誤類型實例,配合instanceof做針對性錯誤處理

P.S.關於具體的幾種錯誤類型以及錯誤處理的更多信息,請查看黯羽輕揚:JS學習筆記8_錯誤處理

七.尊重對象所有權

1.哪些對象不屬於我們?

  • 原生對象(Object、Array等等)

  • DOM對象(比如document)

  • BOM對象(比如window)

  • 庫對象(比如JQuery)

2.具體原則

  1. 不要重寫方法

  2. 不要添新方法,非要改庫功能的話,可以給庫開發插件

  3. 不要刪除方法,不想用的不要刪除,標明過時即可

    注意:delete對原型屬性無效,只對實例屬性有用

    function Fun() {
        this.attr1 = 1; // 實例屬性
    }
    Fun.prototype.attr2 = 2;    // 原型屬性
    
    // 測試
    var obj = new Fun();
    alert(obj.attr1 + ", " + obj.attr2);    // 1, 2
    delete obj.attr1;
    delete obj.attr2;
    alert(obj.attr1 + ", " + obj.attr2);    // undefined, 2
    delete Fun.prototype.attr2;
    alert(obj.attr1 + ", " + obj.attr2);    // undefined, undefined

3.更好的方式

  1. 基於對象的繼承

    也就是clone一個新對象,新對象是屬於自己的,可以隨便改

  2. 基於類型的繼承

    注意不要繼承DOM/BOM/Array,因為支持性不好

    P.S.關於對象繼承/類型繼承的具體實現,請查看黯羽輕揚:重新理解JS的6種繼承方式

  3. 外觀模式

    其實就是組合,因為繼承/組合都是實現代碼復用的方式,關於外觀模式的更多信息請查看黯羽輕揚:設計模式之外觀模式(Facade Pattern)

    一點題外話:facade與adapter的區別是前者創建了新接口,后者只是實現了已存在的接口,作者一針見血

4.polyfill的優缺點

polyfill 是“在舊版瀏覽器上復制標准 API 的 JavaScript 補充”。“標准API”指的是 HTML5 技術或功能,例如 Canvas。“JavaScript補充”指的是可以動態地加載 JavaScript 代碼或庫,在不支持這些標准 API 的瀏覽器中模擬它們。因為 polyfill 模擬標准 API,所以能夠以一種面向所有瀏覽器未來的方式針對這些 API 進行開發,最終目標是:一旦對這些 API 的支持變成絕對大多數,則可以方便地去掉 polyfill,無需做任何額外工作。

關於polyfill的更多信息請查看博客園:[譯]shim和polyfill有什么區別?

優點:不需要的時候很容易去掉

缺點:如果polyfill的實現與標准不完全一致就麻煩了

建議不要用polyfill,應該用原生方法 + 外觀模式來代替,這樣更靈活

5.防篡改

應該開嚴格模式,因為非嚴格模式下靜默失敗難以調試

八.瀏覽器檢測

1.UA(User Agent)檢測

非要UA檢測的話,盡量向后檢測而不是向前,因為后面的不會再變了

P.S.作者認為不用管UA會不會變,理由是會設置UA的用戶應該也知道這樣做的后果

2.特性檢測

特性檢測的一般格式如下:

  1. 嘗試標准方式

  2. 嘗試特定瀏覽器的實現方式

  3. 不支持的話要給出邏輯反饋(比如return null)

例如:

function setAnimation (callback) {
    // 1.嘗試標准方式
    if (window.requestAnimationFrame) {                 // standard
        return requestAnimationFrame(callback);
    }
    // 2.嘗試特定瀏覽器的實現方式
    else if (window.mozRequestAnimationFrame) {         // Firefox
        return mozRequestAnimationFrame(callback);
    }
    else if (window.webkitRequestAnimationFrame) {      //WebKit
        return webkitRequestAnimationFrame(callback);
    }
    else if (window.oRequestAnimationFrame) {           // Opera
        return window.oRequestAnimationFrame(callback);
    }
    else if (window.msRequestAnimationFrame) {          // IE
        return window.msRequestAnimationFrame(callback);
    }
    // 3.不支持的邏輯反饋
    else {
        return setTimeout(callback, 0);
    }
}

3.杜絕特性推斷

不能根據一個特性推斷另一個特性,因為長得像鴨子的東西不一定也像鴨子一樣呱呱叫

4.杜絕瀏覽器推斷

不要根據特性去推斷瀏覽器,比如典型的:

if (document.all) { // IE
    // ...
}

這樣做是不對的,不應該嘗試用特征去描述一個東西,很容易因為條件過少或者過多導致描述不准確

5.到底應該用哪一個?

盡量用直接特性檢測,不行才用UA檢測。至於推斷,就根本不考慮了,沒有任何理由去用推斷

參考資料

  • 《Maintainable JavaScript》


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM