后端程序員的 JavaScript 之旅 - 模塊化(二)

模塊化

上一篇文章介紹了 JavaScript 中如何現實模塊化的一些常見的模式,僅僅了解如何實現 JavaScript 的模塊化模式還是不夠的,對于整個生態圈來說,模塊化規范的制定和遵守更加重要。JS 模塊化缺失帶來的一個嚴重問題是各社區開發一套組件都需要實現自己的模塊化機制,不同社區重復制造輪子,導致組件與組件無法兼容、相互割裂,嚴重阻礙生態系統的發展。這篇文章介紹 JavaScript 生態圈催生出來的比較有影響力的模塊規范 CommonJS、AMD 以及 2015 年正式發布的 ECMAScript 6 Modules 部分。
下面的圖表是來自 Module Counts 的統計數據,可以看到 npm (CommonJS 規范) 的包數量增長曲線異常兇猛,其中一部分原因是 JavaScript 社區對模塊規范需求釋放的力量驚人,由此可見模塊標準對生態圈的重要性。
module counts

CommonJS

CommonJS 的前稱是 ServerJS,由 Mozilla 工程師 Kevin Dangoor 在 2009 年 1 月發起,目標是為非瀏覽器(比如服務端、本地桌面應用、命令行應用)構建 JavaScript 生態系統,同年 8 月改名為 CommonJS,其目標也擴展到瀏覽器。CommonJS 的規范包括模塊(Modules)、包(Package)、Promises 等多個方面,詳情可查閱 CommonJS Wiki。CommonJS 規范有很多的實現,清單可以看這里,其中最著名的實現就是 Node.js,接下來就以它作為例子介紹 CommonJS 的模塊規范。
math.js 文件(也是模塊)的功能是定義數學函數,目前僅實現 add 函數,通過 exports 變量作為其 add 屬性導出。 exports 變量是由加載 math.js 時的底層環境提供的。
increament.js 文件(也是模塊)的功能是定義 increment 函數,其依賴于 math.js 模塊。 這個文件展示了如何定義模塊之外還展示了如何導入模塊,即通過 require 函數導入。 require 函數也是模塊加載的底層環境提供的,具體的用法可以參考 node.js 的官方文檔國內鏡像站點
main.js 展示如何導入模塊以及如何使用導出函數。
math.js

exports.add = function(x, y) {
    return x + y;
};

increment.js

var add = require('math').add;
exports.increment = function(val) {
    return add(val, 1);
};

main.js

var inc = require('increment').increment;
var a = 1;
inc(a); // => 2

module.id == "main";

AMD

AMD 是"Asynchronous Module Definition"的縮寫,意思是「異步模塊定義」,它跟 CommonJS 有一些淵源。AMD 的出現是由于 CommonJS 的模塊規范對瀏覽器支持不友好,當然解決方式有多種多樣,社區也有許多爭論,具體可以參考玉伯(CMD/sea.js 作者)的文章《前端模塊化開發那點歷史》。總之最終 AMD 規范沒有得到 CommonJS 社區的認同決定自立門戶,并且追隨者眾多,實現其規范的佼佼者有 Dojo Toolkit、Require.js 等。
與 CommonJS 相比,AMD 解決了瀏覽器環境異步加載 js 文件避免網頁失去響應以及模塊之間依賴的問題。
現在用 require.js 實現上一節中的示例。
requie.js 通過 define 函數定義模塊,該函數的第一個參數指定當前所定義的模塊 ID,第二個參數指定依賴的模塊列表,第三個參數定義模塊的實現函數,另外前兩個參數是可選的。
AMD 定義函數說明

define(
    module_id /*optional*/, 
    [dependencies] /*optional*/, 
    definition /*function for instantiating the module or object*/
);

目錄結構

  1. index.html 文件中,導入 require.js 的 javascript 標簽中包含 data-main="app" 屬性,表示加載的主數據文件為 app.js 。
  2. app.js 中通過 requirejs.config 配置目錄結構,并且加載 main.js 文件。
  3. main.js 通過 define 作為入口導入帶 require 參數的匿名函數,匿名函數體內實現具體功能,通過 require 函數導入其他模塊。
  4. increment.js 和 math.js 按規規范,通過 define 函數實現模塊。
目錄結構

math.js

define('math', function() {
    var add = function(x, y) {
        return x + y;
    };
    return {
        add: add
    };
});

increment.js

define('increment', ['math'], function(math) {
    var add = math.add;
    var increment = function(val) {
        return add(val, 1);
    };
    return {
        increment: increment
    };
});

main.js

define(function (require) {
    var inc = require('increment').increment;
    console.log('inc => ' + inc);
    var a = 1;
    console.log(inc(a, 1)); // => 2
});

app.js

requirejs.config({
    baseUrl: 'lib',
    paths: {
        app: '../app'
    }
});

requirejs(['app/main']);

index.html

<!DOCTYPE html>
<html>
    <head>
        <script data-main="app" src="lib/require.js"></script>
    </head>
    <body>
        <h1>Hello World</h1>
    </body>
</html>

UMD/CMD

UMD 是 "Universal Module Definition" 的縮寫,提供了一系列模式同時兼容 CommonJS 和 AMD ,代價是代碼有些丑陋。
CMD 是 "Common Module Definition" 的縮寫,是國內的前端大牛玉伯提出的,實現是 sea.js ,在國內比較知名。不過目前作者已經宣布放棄 sea.js,可以參考 github 上的討論:《感覺一直追趕的SeaJS已死》

ECMAScript 6 Modules

令人振奮的消息是 2015 年 6 月正式發布的 ECMAScript 6 包含了模塊規范,采用申明式的語法,使用 import、export 這兩個關鍵字,同時照顧到 Common.JS 社區和 AMD 社區的使用習慣,方便地實現模塊的定義和導入。
我們再用 ES6 的語法實現一遍上面兩節的示例。
math.js

export function add(x, y) {
    return x + y;
};

increment.js

import * as math from 'math';

var add = math.add;
exports function increment(val) {
    return add(val, 1);
};

main.js

import { increment } from 'increment';

var a = 1;
increment(a); // => 2

ES6 正式發布到目前為止還不到半年時間,推廣還需要很長一段時間。目前主流的瀏覽器比如 Chorme、Firefox 還僅支持部分 ES6 的特性,具體可以參考 ECMAScript 6 compatibility table,所以目前正在進行的項目押寶在 ES 6 Modules 具有一定風險。
目前想使用最新的 ES6 新特性可以:

  • traceur 將ES6模塊編譯為AMD規范或者CommonJS規范的模塊
  • es6 module transpiler 是Google的轉換編譯器,目的在于支持許多Javascript的特性包括ES6模塊
  • TypeScript 微軟出品的 JavaScript 超集語言,也支持 ES6 模塊

參考:
前端模塊化開發那點歷史
JavaScript 模塊化七日談
深入淺出Node.js
RequireJS.org
Writing Modular JavaScript With AMD, CommonJS & ES Harmony
ECMAScript 6 Modules: What Are They and How to Use Them Today
ECMAScript 6 modules: the final syntax

后端程序員的 JavaScript 之旅 - 模塊化系列文章:
后端程序員的 JavaScript 之旅 - 模塊化(一) | 簡書
后端程序員的 JavaScript 之旅 - 模塊化(二) | 簡書

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容