深入理解JS模塊

引言

JavaScript的模塊機制其實是借鑑的其他程序設計語言的, 如Java中package的概念, import java.util.ArrayList;; package就是邏輯上相關的代碼組織到同一個包內,包內是一個相對獨立的作用域,不用擔心命名衝突等等, 當需要在外部使用的是否直接import相應的package即可。

由於JavaScript在設計之初的定位原因, 並沒有提供類似模塊的功能, 隨後便出現了各種模擬類似的功能的規範。到今天(2018-5-28)ES6已經十分普及, ES6的模塊機制已經大規模使用, 我們完全可以使用ES6提供的模塊化規範(機制)。

類模塊化

類模塊化: 這是我自己理解的一個模塊化概念, 指的是像函數封裝, 對象, 立即執行函數包裝這樣的類似模塊化的規範。

函數封裝

函數就是對實現特定邏輯的一組語句的打包, JS的作用域也是基於函數的, 所以函數可以很自然的作爲模塊化, 這也是最開始實現模塊化的一種方法。

 

function func1(){
    ...
}
function func2(){
    ...
}

引用模塊也即是調用函數, 存在污染全局變量的缺點, 變量衝突等缺點。

對象

 

var myModule = {
    var1: 1,
    var2: 2,
    func1: function(){
        ...
    },
    func2: function(){
        ...
    }
}

將上面的函數封裝在一個對象中, 引用模塊即引用相應文件中對象上的屬性, 如: myModule.func1(), 通過對象名(模塊名)避免了全局變量污染, 但是存在安全問題, 如: 外部可以隨意修改模塊內部的屬性和方法等。

立即執行函數

 

var myModule = (function(){
    var var1 = 1;
    var var2 = 2;
    function func1(){
        ...
    }
    function func2(){
        ...
    }
    return {
        func1: func1,
        func2: func2
    };
})();

在上面對象的基礎之上, 用立即執行函數進行封裝, 可以解決全局變量污染, 防止模塊內部屬性和方法被外部修改, 這是當前主流模塊規範的基礎。

CommonJS(NodeJS)

CommonJS: 通用模塊規範, 主要由NodeJS具體實現; 根據CommonJS規範, 一個單獨的文件就是一個模塊。每一個模塊都是一個單獨的作用域, 也就是說, 在該模塊內部定義的變量, 無法被其他模塊讀取, 除非定義爲global(瀏覽器中爲window)對象的屬性。

CommonJS模塊例子:

 

//模塊定義 myModule.js
var name = 'Byron';
function printName(){
    console.log(name);
}
function printFullName(firstName){
    console.log(firstName + name);
}
module.exports = {
    printName: printName,
    printFullName: printFullName
}
//加載模塊
var myModule = require('./myModule.js');
myModule.printName();

CommonJS模塊存在的問題

require引入模塊是同步的, 由於在瀏覽器環境下, JS都是通過script標籤引入, 而這是天生異步的, 因此CommonJS在瀏覽器環境下無法正常加載(無法處理依賴問題)。NodeJS廣泛採用CommonJS的原因主要是NodeJS的require模塊都是在本地, 完全不用擔心異步過程(即使在服務器上也是如此)。因此, 針對瀏覽器端異步require模塊出現了AMDCMD規範。

AMD(RequireJS)

AMD: Asynchronous Module Definition(異步模塊定義), 在瀏覽器端模塊化開發的規範, 不是JavaScript原生支持, RequireJS是AMD規範的具體實現(嚴格上說是RequireJS的推廣中產生的AMD規範)。

RequireJS模塊例子:

 

// 定義模塊 myModule.js
define('myModule', ['dependency'], function(){
    var name = 'Byron';
    function printName(){
        console.log(name);
    }
    return {
        printName: printName
    };
});

// 加載模塊
require(['myModule'], function (my){
  my.printName();
});

RequireJS定義了一個全局函數define(id?, dependencies?, factory);來創建一個模塊。
AMD模塊中所有的依賴都前置, 其模塊是異步的, 該自定義的模塊內用到的模塊均等到異步加載完成之後才調用響應模塊, 這樣瀏覽器不會失去響應。require指定的回調函數,只有前面的模塊都加載成功後,纔會運行,解決了依賴性的問題。

例如: 現需要在一個HTML頁面中需要使用jQuery-fileupload插件, 並通過script標籤的方式引入JS文件, 傳統的方式是先引入jquery.min.js再引入jquery.fileupload.js。由於jqery.fileupload.js是基於jQuery的, 必須保證首先引入jQuery, 加載JS時候頁面會停止若此時網絡較差, 會導致頁面失去響應時間較長。

總結

RequireJS主要解決如問題:

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

模塊機制用途:

  1. CommonJS是同步的, 主要用於服務器
  2. AMDCMD是異步的, 兩者的模塊定義和加載機制稍有不同, 主要用於瀏覽器

AMD與CMD的區別

  1. AMD推崇依賴前置,在定義模塊的時候就要聲明其依賴的模塊,CMD`推崇就近依賴,只有在用到某個模塊的時候再去require
  2. 兩個都是定義的全局define函數來定義模塊, define接收函數function(require, exports, module)保持一致
  3. CMD是懶加載, 僅在require時纔會加載模塊; AMD是預加載, 在定義模塊時就提前加載好所有依賴
  4. CMD保留了CommonJS風格

CMD(SeaJS)

CMD: Common Module Definition通用模塊定義, 由國內發展出來, SeaJS是其典型代表, 即SeaJS是通過瀏覽器對CMD的具體實現

SeaJS模塊例子:

 

// 定義模塊  myModule.js
define(function(require, exports, module) {
  var $ = require('jquery.js');
  var foo = require('foo');
  var out = foo.bar();
  $('div').addClass('active');
  module.exports = out;
});

// 加載模塊
seajs.use(['myModule.js'], function(my){

});

SeaJS定義了一個全局函數define(id?, deps?, factory)來創建一個模塊, define接受一個需要三個參數的函數, 分別爲:

  • require: 一個方法, 接受模塊標識 作爲唯一參數,用來獲取其他模塊提供的接口:require(id)
  • exports: 一個對象, 用來向外提供模塊接口
  • module: 一個對象, 上面存儲了與當前模塊相關聯的一些屬性和方法

CMD推崇依賴就近原則(也就是懶加載), 模塊內部的依賴在需要引入的時候再引入, 如上例中的var $ = require('jquery.js'), 這一點和通用的CommonJS模塊風格保持一致。

UMD

UMD: 是一個既能在seajs(CMD)環境裏引入,又能在requirejs(AMD)環境中引入,
當然也能在Node.js(CommonJS)中使用,另外還可以在沒有模塊化的環境中用script標籤全局引入的'模塊規範'

UMD模塊其實就是在當前JS執行環境中對以上幾種模塊規範定義的define, module.exports等進行判斷, 同一模塊根據不同場所返回不同結果。

UMD模塊例子:

 

;(function (global) {
    function factory () {
        var moduleName = {};
        return moduleName;
    }
    if (typeof module !== 'undefined' && typeof exports === 'object') {
        module.exports = factory();
    } else if (typeof define === 'function' && (define.cmd || define.amd)) {
        define(factory);
    } else {
        global.moduleName = factory();
    }
})(typeof window !== 'undefined' ? window : global);

UMD模塊在不同環境引入:

 

// Node.js
var myModule = require('moduleName');
// SeaJs
define(function (require, exports, module) {
    var myModule = require('moduleName');
});
// RequireJs
define(['moduleName'], function (moduleName) {

});
// Browse global
<script src="moduleName.js"></script>

ES6模塊(import,export)

ES6在語言標準的層面上, 實現了模塊功能, 而且實現得相當簡單, 完全可以取代CommonJSAMD規範, 是瀏覽器和服務器通用的模塊解決方案。

ES6模塊例子:

 

//模塊定義 myModule.js
const name = 'Byron';
function printName(){
    console.log(name);
}
function printFullName(firstName){
    console.log(firstName + name);
}
const myModule = {
    printName: printName,
    printFullName: printFullName
};
export myModule;

//加載模塊
import myModule, { printFullName } from './myModule.js';
myModule.printName();
printFullName('Michael');

注意

  1. ES6中的export是ES6對於JS模塊的一種新的規範, 不同於CommonJS規範中的module.exportsexports;
  2. CommonJS規範中exports可以理解爲指向module.exports的一個指針, 可以exports.newModule = {...}, 但是這樣寫exports={..}是不行的, 這會將exports這個指針指向新的{...}對象, 不再指向module.exports;
  3. ES6語法一般都經過babel轉義爲JS, 故可以在ES6中使用CommonJS模塊規範, 如: var myModule = require('./myModule.js')

參考文章

  1. js模塊化編程之徹底弄懂CommonJS和AMD/CMD
  2. 寫一個適應所有環境的js模塊
  3. SeaJS
  4. RequireJS
  5. 阮一峯-ES6模塊



作者:dino小恐龍
鏈接:https://www.jianshu.com/p/5226bd9644b6
來源:簡書
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章