前端模塊化的前世此生

凡是開發大型應用程序,模塊塊必然是不可或缺的一部分。那麼什麼是模塊化呢?其實模塊化就是將一個複雜的系統分解成多個獨立的模塊的代碼組織方式。在很長的一段時間裏,前端只能經過一系列的<script>標籤來維護咱們的代碼關係,可是一旦咱們的項目複雜度提升的時候,這種簡陋的代碼組織方式即是如噩夢般使得咱們的代碼變得混亂不堪。因此,在開發大型Javascript應用程序的時候,就必須引入模塊化機制。因爲早期官方並無提供統一的模塊化解決方案,因此在羣雄爭霸的年代,各類前端模塊化方案層出不窮。本文將從最先期的IFEE閉包方案到如今的ES6 Modules, 追根溯源,帶你詳細瞭解前端模塊化的前世此生。javascript

IIFE

模塊化的一大做用就是用來隔離做用域,避免變量衝突。而Javascript沒有語言層面的命名空間概念,只能將代碼暴露到全局做用域下。在刀耕火種的年代,做爲腳本語言的Javascript爲了不全局變量污染,只能使用閉包來實現模塊化。好在咱們能夠利用自執行函數(IIFE)來執行代碼,從而避免變量名泄漏到全局做用域中:css

(function(window) {
	window.jQuery = {
       // 這裏是代碼 
    };
})(window);
複製代碼

雖然IIFE能夠有效解決命名衝突的問題,可是對於依賴管理,仍是一籌莫展。因爲瀏覽器是從上至下執行腳本,所以爲了維持腳本間的依賴關係,就必須手動維護好script標籤的相對順序。html

AMD

AMD (Asynchronous Module Definition)也是一種 JavaScript模塊化規範。從名字上能夠看出,它主要提供了異步加載的功能。對於多個JS模塊之間的依賴問題,若是使用原生的方式加載代碼,隨着加載文件的增多,瀏覽器會長時間地失去響應,而AMD可以保證被依賴的模塊儘早地加載到瀏覽器中,從而提升頁面響應速度。因爲該規範原生Javascript沒法支持,因此必須使用相應的庫來實現對應的模塊化。RequireJS就是實現了該規範的類庫,實際上AMD也是其在推廣過程當中的產物。前端

利用RequireJS來編寫模塊,全部的依賴項必須提早聲明好。在導入模塊的時候,也會先加載對應的依賴模塊,而後再執行接下來的代碼,同時AMD模塊能夠並行加載全部依賴模塊,從而很好地提升頁面加載性能:java

define('./index.js',function(code){
    // code 就是index.js 返回的內容
    return {
        sayHello: function(name) {
            return "Hello, " + name;
        }
    }
});

複製代碼

CMD

CMD(Common Module Definition)最初是由阿里的玉伯提出的,同AMD相似,使用CMD模塊也須要使用對應的庫SeaJS。SeaJS所要解決的問題和requireJS同樣,可是在使用方式和加載時機上有所不一樣:node

define(function(require) {

   //經過require引用模塊

   var path=require.resolve('./cmdDefine');

   alert(path);

});
複製代碼

CMD加載完某個依賴模塊後並不執行,只是下載而已,在全部依賴模塊加載完成後進入主邏輯,遇到require語句的時候才執行對應的模塊,這樣模塊的執行順序和書寫順序是徹底一致的。若是使用require.async()方法,能夠實現模塊的懶加載。jquery

CommonJS

隨着Javasript應用進軍服務器端,業界急需一種標準的模塊化解決方案,因而,CommonJS(www.commonjs.org)應運而生。它最初是由Kevin Dangoor在他的這篇博文中首次提出。webpack

這是一種被普遍使用的Javascript模塊化規範,你們最熟悉的Node.js應用中就是採用這個規範。在Node.js中,內置了module對象用來定義模塊, require函數用來加載模塊文件,代碼以下:git

// utils.js 模塊定義
var add = function(a, b) {
    return a + b;
};
module.exports = {
    add: add
};

// 加載模塊
var utils = require('./utils');
console.log(utils.add(1, 2));
複製代碼

此種模塊化方案特色就是:同步阻塞式加載,沒法實現按需異步加載。另外,若是想要在瀏覽器中使用CommonJS模塊就須要使用Browserify進行解析:es6

npm install browserify -g
browserify utils.js >&emsp;bundle.js
複製代碼

固然,你也可使用gulp, webpack等工具進行解析打包後引入到瀏覽器頁面中去。

UMD

上面介紹的CommonJS和AMD等模塊化方案都是針對特定的平臺,若是想要實現跨平臺的模塊化,就得引入UMD的模塊化方式。UMD是通用模塊定義(Universal Module Definition)的縮寫,使用該中模塊化方案,能夠很好地兼容AMD, CommonJS等模塊化語法。

接下來,讓咱們經過一個簡單地例子看一下如何使用和定義UMD模塊:

(function(root, factory) {

  if(typeof define === 'function' && define.amd) {

    define(['jquery'], factory);

  } else if(typeof module === 'object' &&

    typeof module.exports === 'object') {

    var jquery = require('jquery');

    module.exports = factory(jquery);

  } else {
      
    root.UmdModule = factory(root.jQuery);
  
  }

}(this, function(jquery) {
	// 如今你能夠利用jquery作你想作的事了
    
}));

複製代碼

這種模塊定義方法,能夠看作是IIFE的變體。不一樣的是它倒置了代碼的運行順序,須要你將所需運行的函數做爲第二個參數傳入。因爲這種通用模塊的適用性強,不少JS框架和類庫都會打包成這種形式的代碼。

ES6 Modules

對於ES6來講,沒必要再使用閉包和封裝函數等方式進行模塊化支持了。在ES6中,從語法層面就提供了模塊化的功能。然而受限於瀏覽器的實現程度,若是想要在瀏覽器中運行,仍是須要經過Babel等轉譯工具進行編譯。ES6提供了importexport命令,分別對應模塊的導入和導出功能。具體實例以下:

// demo-export.js 模塊定義
var name = "scq000"
var sayHello = (name) => {
  console.log("Hi," + name);
}
export {name, sayHello};

// demo-import.js 使用模塊
import {sayHello} from "./demo-export";
sayHello("scq000");
複製代碼

對於具體的語法細節,想必你們在平常使用過程當中都已經輕車熟路了。但對於ES6模塊化來講,有如下幾點特性是須要記住的:

  • ES6使用的是基於文件的模塊。因此必須一個文件一個模塊,不能將多個模塊合併到單個文件中去。
  • ES6模塊API是靜態的,一旦導入模塊後,沒法再在程序運行過程當中增添方法。
  • ES6模塊採用引用綁定(能夠理解爲指針)。這點和CommonJS中的值綁定不一樣,若是你的模塊在運行過程當中修改了導出的變量值,就會反映到使用模塊的代碼中去。因此,不推薦在模塊中修改導出值,導出的變量應該是靜態的。
  • ES6模塊採用的是單例模式,每次對同一個模塊的導入其實都指向同一個實例。

Webpack中的模塊化方案

做爲現代化的前端構建工具,Webpack還提供了豐富的功能可以使咱們更加輕易地實現模塊化。利用Webpack,你不只能夠將Javascript文件進行模塊化,同時還能針對圖片,css等靜態資源進行模塊化。你能夠在代碼裏使用CommonJS, ES6等模塊化語法,打包的時候你也能夠根據需求選擇打包類型,如UMD, AMD等:

module.exports = {
  //...
  output: {
    library: 'librayName',
    libraryTarget: 'umd', // 配置輸出格式
    filename: 'bundle.js'
  }
};
複製代碼

另外,ES6模塊好處不少,可是並不支持按需加載的功能, 而按需加載又是Web性能優化中重要的一個環節。好在咱們能夠藉助Webpack來彌補這一缺陷。Webpack v1版本提供了require.ensureAPI, 而2.x以後使用了import()函數來實現異步加載。具體的代碼示例能夠查看我以前所寫的前端性能優化之加載技術 這篇文章。

總結

模塊化方案解決了代碼之間錯綜複雜的依賴關係,不只下降了開發難度同時也讓開發者將精力更多地集中在業務開發中。隨着ES6標準的推出,模塊化直接成爲了Javascript語言規範中的一部分。這也意味着CommonJS, AMD, CMD等模塊化方案終將退出歷史的舞臺。固然,要實現徹底ES6模塊化的使用,還須要一段長時間的等待。那麼,在這段過渡的時間裏,咱們可能仍然須要維護舊有的代碼,使用傳統的模塊化方案來構建應用。對於前端工程師來講,系統地瞭解主流的模塊化方案就顯得十分必要了。最後,讓咱們再一次回顧一下各類模塊化方式的特色:

模塊化方案 加載 同步/異步 瀏覽器 服務端 模塊定義 模塊引入
IFEE 取決於代碼 取決於代碼 支持 支持 IFEE 命名空間
AMD 提早預加載 異步 支持 構建工具r.js define require
CMD 按需加載 延遲執行 支持 構建工具spm define define
Common 值拷貝,運行時加載 同步 原生不支持,須要使用browserify提早打包編譯 原生支持 module.exports require
UMD 取決於代碼 取決於代碼 支持 支持 IFEE 命名空間
ES Modules(ES6) 實時綁定,動態綁定,編譯時輸出 同步 需用babel轉譯 需用babel轉譯 export import

參考資料

javascript.ruanyifeng.com/nodejs/modu…

《你不知道的Javascript》

www.infoq.cn/article/es6…

——本文首發於我的公衆號,轉載請註明出處———

微信掃描二維碼,關注個人公衆號
最後,歡迎你們關注個人公衆號,一塊兒學習交流。
相關文章
相關標籤/搜索