1. 程式人生 > >JavaScript中的模組化開發

JavaScript中的模組化開發

一、為什麼會有模組化


1. 當一個專案開發的越來越複雜的時候,會遇到一些問題,比如:

  • 命名衝突:當專案由團隊進行協作開發的時候,不同開發人員的變數和函式命名可能相同;即使是一個開發,當開發週期比較長的時候,也有可能會忘記之前使用了什麼變數,從而導致重複命名,導致命名衝突。

  • 檔案依賴:程式碼重用時,引入js檔案的數目可能少了,或者引入的順序不對,比如使用boostrap的時候,需要引入jQuery,並且jQuery的檔案必須要比boostrap的js檔案先引入。

2. 當使用模組化開發的時候可以避免以上的問題,並且讓開發的效率變高,以及方便後期的維護:

  • 提升開發效率

    :程式碼方便重用,別人開發的模組直接拿過來就可以使用,不需要重複開發法類似的功能。

  • 方便後期維護:程式碼方便重用,別人開發的模組直接拿過來就可以使用,不需要重複開發法類似的功能。

所以總結來說,在生產角度,模組化開發是一種生產方式,這種方式生產效率高,維護成本低。從軟體開發角度來說,模組化開發是一種開發模式,寫程式碼的一種方式,開發效率高,方便後期維護。

二、模組化開發的演變過程


1. 全域性函式

function add(a , b) {
    return parseFloat(a) + parseFloat(b);
}
function substract(a ,b) {}
function multiply(a ,b) {}
function divide(a ,b) {}

在早期的開發過程中就是將重複的程式碼封裝到函式中,再將一系列的函式放到一個檔案中,這種情況下全域性函式的方式只能認為的認為它們屬於一個模組,但是程式並不能區分哪些函式是同一個模組,如果僅僅從程式碼的角度來說,這沒有任何模組的概念。

存在的問題:

  • 汙染了全域性變數,無法保證不與其他模組發生變數名衝突。
  • 模組成員之間看不出直接關係。

2. 物件封裝-名稱空間

var calculator = {
  add: function(a, b) {
    return parseFloat(a) + parseFloat(b);
  },
  subtract: function(a, b) {},
  multiply: function(a, b) {},
  divide: function(a, b) {}
};

通過新增名稱空間的形式從某種程度上解決了變數命名衝突的問題,但是並不能從根本上解決命名衝突。 不過此時從程式碼級別可以明顯區分出哪些函式屬於同一個模組。

存在的問題:

  • 暴露了所有的模組成員,內部狀態可以被外部改寫,不安全。
  • 名稱空間越來越長。

3. 私有公有成員分離

var calculator = (function () {
    // 這裡形成一個單獨的私有的空間
    // 私有成員的作用:
    //   1、將一個成員私有化
    //   2、抽象公共方法(其他成員中會用到的)
    
    // 私有的轉換邏輯
    function convert(input){
        return parseInt(input);
    }

    function add(a, b) {
        return convert(a) + convert(b);
    }
    function subtract(a, b) {}
    function multiply(a, b) {}
    function divide(a, b) {}
    return {
        add : add,
        subtract : subtract,
        multiply : multiply,
        divide : divide
    }
})();
  1. 利用此種方式將函式包裝成一個獨立的作用域,私有空間的變數和函式不會影響到全域性作用域。
  2. 以返回值的方式得到模組的公共成員,公開公有方法,隱藏私有空間內部的屬性、元素,比如註冊方法中可能會記錄日誌。
  3. 可以有選擇的對外暴露自身成員。
  4. 從某種意義上來說,解決了變數命名衝突的問題。

4. 模組的擴充套件與維護

// 計算模組
(function (calculator) {
    function convert(input) {
        return parseInt(input);
    }
    calculator.add = function(a, b) {
        return convert(a) + convert(b);
    }
    window.calculator = calculator;
})(window.calculator || {});

// 新增需求
(function (calculator) {
    calculator.remain = function (a , b) {
        return a % b;
    }
    window.calculator = calculator;
})(window.calculator || {});
        
alert(calculator.remain(4,3));
  1. 利用此種方式,有利於對龐大的模組的子模組劃分。
  2. 實現了開閉原則:對新增開放,對修改關閉。對於已有檔案儘量不要修改,通過新增新檔案的方式新增新功能。

5. 第三方依賴的管理

(function (calculator , $) {
    // 依賴函式的引數,是屬於模組內部
    // console.log($);
    calculator.remain = function (a , b) {
        return a % b;
    }
    window.calculator = calculator;
})(window.calculator || {} , jQuery);

模組最好要保證模組的職責單一性,最好不要與程式的其他部分直接互動,通過向匿名函式注入依賴項的形式,除了保證模組的獨立性,還使模組之間的以來關係變得明顯。
  對於模組的依賴通過自執行函式的引數傳入,這樣做可以做到依賴抽象,本例中使用的jQuery,而當要使用zepto的時候,只要更換傳入的引數即可。
  原則:高內聚低耦合,模組內相關性高,模組間關聯低。

總結:在什麼場景下使用模組化開發

  • 業務複雜
  • 重用邏輯非常多
  • 擴充套件性要求較高

三、模組化規範


伺服器端規範主要是CommonJSnode.js用的就是CommonJS規範。
  客戶端規範主要有:AMD(非同步模組定義,推崇依賴前置)、CMD(通用模組定義,推崇依賴就近)。AMD規範的實現主要有RequireJSCMD規範的主要實現有SeaJSRequireJS在國外用的比較多,SeaJS在國內用的比較多,並且SeaJS的創始人為阿里的玉伯,所以SeaJS在阿里系用的非常廣泛,包括京東等大廠也在用SeaJS,我們詳細介紹的也是SeaJS。但是SeaJS已經停止維護了,因為在ES6中已經有了模組化的實現,隨著ES6的普及,第三方的模組化實現將會慢慢的淘汰(但是這個在國內可能還要很多年)。