深入理解模組化程式設計

小周sri的碼農發表於2017-09-18

1.模組化開發規範

JavaScript中所有物件的屬性都是公共的,並沒有什麼明確的方法來表明屬效能否從物件的外部被訪問,而有時候我們並不希望物件的屬性被外界訪問。一種方式方式通過命名約定的形式,比如在變數的前面加下劃線(_)。還有一些其他的方式是屬性完全私有化。

2.為什麼要模組化

在模組化沒有出現之前,我們JavaScript指令碼大概是這樣的:

<script src="module1.js"></script>
    <script src="module2.js"></script>
    <script src="module3.js"></script>
    <script src="module4.js"></script>
    ....

但我們引入多個js檔案時,會存在一些問題:

  • 把所有的程式碼放到全域性環境會引起衝突
  • 各個指令碼必須按照嚴格的依賴順序放置
  • 在大型的專案中,可能會引入很多的js指令碼,script就會變得很多,並且難以維護。

為了解決這些問題,湧現了各種模組化的方案。

3.模組化的方式

這種方式是建立物件的一種方法,用於建立具有私有屬性的物件。基本思路是使用一個立即執行的函式表示式,返回一個物件。該函式表示式可以包含任意數量的區域性變數,這些變數從函式的外部是無法訪問到的。因為返回的物件是在自執行函式內部宣告的,所以物件中定義的方法可以訪問自執行函式內的區域性變數,這些方法被具有特權的方法。

 var p2 = (function(){

            var money = 30000;

            return {
                name: 'lisi',
                sayMoney: function(){
                    return money;
                }
            };

        }());

4. CommonJS

我們在前端的js程式碼在沒有模組化之前也能正常執行,但是在伺服器端的js指令碼必須要被模組化才能正常工作。所以雖然JavaScript在前端發展了這麼多年,第一個流行的模組化規範卻是由伺服器端的js應用發展起來的。CommonJS規範是由NodeJS發揚光大的。

  1. 定義模組

在CommonJS規範中,一個單獨的JS檔案就是一個模組。每個模組都是一個單獨的作用域,也就是說,在該模組內部定義的變數,無法被其他模組讀取,除非定義為global物件的屬性。

  1. 模組輸出

模組只有一個出口,module.exports物件,我們需要把需要輸出的內容放入該模組

  1. 載入模組

載入模組使用require()方法,該方法讀取一個檔案並執行,返回檔案內部的module.exprots物件

例如,我們寫了這樣一個檔案myModule1.js

var name = '無忌';
function sayName(){
    console.log(name);
};

function sayFullName(firstName){
    console.log(firstName + name);
};
module.exports = {
    printName:sayName,
    printFullName:sayFullName
};

我們的模組定義好了,那我們怎樣使用這個模組呢?例如,我們建立了myModule2.js檔案:

var module1 = require('./myModule1.js');

module1.printName();
module1.printFullName('張');

在node環境下,require方法在引入其他模組的時候是同步的,可以輕鬆的控制模組的引入順序,保證了模組之間的依賴順序。但是在瀏覽器中卻不是這樣的,因為我們的<script>標籤天生非同步,在載入js檔案的時候是非同步的,也就意味著不能保證模組之間的正確依賴。

5. AMD規範

AMD即Asynchronous Module Definition,非同步模組定義。它是在瀏覽器端實現模組化開發的規範。由於該規範不是JavaScript原始支援的,使用AMD規範進行開發的時候需要引入第三方的庫函式,也就是鼎鼎大名的RequireJS

RequireJS主要解決兩個問題:

  1. 多個js檔案可能有依賴關係,被依賴的檔案需要早於依賴它的檔案載入到瀏覽器。
  2. js載入的時候瀏覽器會停止頁面渲染,載入的檔案越多,頁面失去響應的時間越長。

  3. 定義模組

RequireJS定義了一個define函式,用來定模組。

define(id, [dependencies], factory);

  • id:可選引數,用來定義模組的標識,如果沒有提供引數的話,預設為檔案的名字。
  • dependencies:當前模組依賴的其他模組,陣列。
  • factory:工廠方法,初始化模組需要執行的函式或物件。如果為函式,它只被執行一次。如果是物件,此物件會作為模組的輸出值。
// amdModule1.js

define('amdModule1',[],function(){
    
    console.log('模組一');
    
    var name= '張三';
    
    var money = 1000;
    
    var sayName = function(){
        return name;
    }
    var sayMoney = function(){
        return money;
    }
    return {
        sayName:sayName,
        sayMoney:sayMoney
    }
})

// module2.js


define('amdModule2',['amdModule1'],function(amdModule1){
    
    console.log('模組二');
    
    var name = '李四';
    
    var weight = 80;
    
    var sayName = function(){
        return amdModule1.sayName();
    }
    var sayWeight = function(){
        return weight;
    }
    return {
        sayWeight:sayWeight,
        name:name,
        sayName:sayName
    };
})
  1. 在頁面中載入模組

載入模組需要使用require()函式。

require([dependencies], function(){});

  • dependencies:該引數是一個陣列,表示所依賴的模組。
  • function:這是一個回撥函式,當所依賴的模組都載入成功之後,將呼叫該回撥方法。依賴的模組會以引數的形式傳入該函式,從而在回撥函式內部就可以使用這些模組。
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
    </head>
    <body>
        <script src="require.js"></script>
        <script type="text/javascript" src="amdModule1.js"></script>
        <script type="text/javascript" src="amdModule2.js"></script>
        <script type="text/javascript">
            require(['amdModule1','amdModule2'],function(amdModule1,amdModule2){
                console.log(amdModule1.sayName());  //張三
                console.log(amdModule1.name); //undefined
                console.log(amdModule2.sayName()); //張三
                console.log(amdModule2.sayWeight()); //80
            });
        </script>
    </body>
</html>

require()函式在載入依賴的模組時,是非同步載入的,這樣瀏覽器就不會失去響應,回撥函式只有在所依賴的全部模組都被被成功載入之後才執行,因此,解決了依賴性的問題。

6. CMD規範

CMD即Common Module Definition通用模組定義。它解決的問題和AMD規範是一樣的,只不過在模組定義方式和模組載入時機上不同。CMD也需要額外的引入第三方的庫檔案,SeaJS。

SeaJS推崇一個模組一個檔案,遵循統一的寫法:

define(id, dependencies, factory);

因為CMD推崇一個檔案一個模組,所以經常使用檔名作為模組的ID;CMD推崇就近原則,所以一般不再define的引數中寫依賴,在factory函式中寫。

  • require:我們定義的模組可能會依賴其他模組,這個時候就可使用require()引入依賴。
  • exports:等價於module.exports,只是為了方便我們使用
  • module.exports:用於存放模組需要暴露的屬性
  1. 定義模組
// cmdModule1.js

define(function(require,exports,module){
    var name = '張三';
    function sayName(){
        return name;
    }
    module.exports={
        sayName:sayName
    }
})

// cmdModule2.js


define(function(require,exports,module){
    
    var cmdModule1 = require('cmdModule1.js');
    console.log('模組');
    function sayName(){
        return cmdModule1.sayName();
    }
    module.exports={
        sayName:sayName
    }
})
  1. 使用模組
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
    </head>
    <body>
        <script src="sea.js"></script>
        <script src="cmdModule2.js"></script>
        <script src="cmdModule1.js"></script>
        <script type="text/javascript">
            seajs.use(['cmdModule2.js','cmdModule1.js'],function(cmdModule2,cmdModule1){
                console.log(cmdModule2.sayName());
                console.log(cmdModule1.sayName());
            })
        </script>
    </body>
</html>

7. AMD和CMD規範的區別

AMD在載入模組完成後會立即執行該模組,所有的模組都載入完成後執行require方法中的回撥函式,執行主邏輯,這樣的效果就是依賴模組的執行順序和書寫順序不一定一致,看網速,誰先下載下來,誰先執行,但是我們的主邏輯一定是在所有的依賴模組都被載入完成後才執行。

CMD載入完某個模組的時候並不執行,只是把它們下載下來而已,在所有的模組下載完成後,當使用require請求某個模組的時候,才執行對應的模組。

相關文章