瞅瞅JavaScript模塊標準

模塊是每門語言構建復雜系統(tǒng)的必備特性彤侍,JavaScript自然也不例外英支。JavaScript當前流行的模塊化標準有CommonJS改化、AMD掩蛤、CMD、ES6等等陈肛,本文對這些標準做了簡單梳理揍鸟,努力做到應用時不懵逼,不亂用句旱。

模塊

現(xiàn)如今幾乎每門語言都有自己的模塊化解決方案阳藻,這是隨著軟件工程越來越復雜的必然產(chǎn)物晰奖。貼幾個流行語言的模塊化介紹大家感受下:

所有語言的模塊化解決方案都是為了實現(xiàn)將復雜的程序拆分成獨立的幾個模塊,每個模塊寫明自己的依賴腥泥、輸出自己的能力畅涂。模塊化讓復雜代碼變得容易維護、方便復用道川。

概覽

JavaScript標準眾多午衰,縷清這幾個標準的發(fā)展史有助于大家選擇采用哪種方案來寫代碼。

  1. CommonJS應該是最早在民間自發(fā)產(chǎn)生的服務端模塊化標準冒萄,一開始是叫ServerJS臊岸,后來改名了。
  2. 服務端JS有了模塊化標準之后尊流,瀏覽器JS表示我也必須有帅戒,于是基于CommonJS標準產(chǎn)生了AMD,和CommonJS相比最大的不同就是依賴的異步加載崖技。
  3. CMD是類似AMD的對于瀏覽器JS模塊化標準逻住,源自Sea.js。
  4. ES6則是集大成者迎献,其統(tǒng)一了同步和異步的模塊化標準瞎访,試圖讓JS模塊化標準從分裂走向統(tǒng)一,并取得了不小的成績吁恍。

標準定制一般都是和實現(xiàn)相輔相成的扒秸,那么JS這些有名的模塊化標準主要都有哪些實現(xiàn)呢?

CommonJS AMD CMD ES6
Node.js/RingoJS RequireJS/curl.js SeaJS ES6

每個標準都在JS世界的不同領域中得到廣泛的應用冀瓦,對這些標準進行初步的了解是有必要的伴奥。

CommonJS

為了方便,直接使用Node.js的模塊化實現(xiàn)來說明CommonJS標準翼闽。下面給出按照CommonJS標準寫的demo拾徙,隨后其他標準的demo也會實現(xiàn)一樣的功能。

// math.js
const { PI } = Math;
exports.area = (r) => PI * r ^ 2;
exports.circumference = (r) => 2 * PI * r;
console.log(module);

// main.js
var area = require('./math').area;
var result = area(3);
console.log(result);

CommonJS模塊定義了三個變量感局,module尼啡,exportsrequire

module

通過console.log(module)蓝厌,我們可以打印出module的結構如下:

Module {
  id: '.',                                                      // 模塊Id玄叠,一般都是文件的絕對路徑
  exports: { area: [Function], circumference: [Function] },     // 模塊對外輸出的變量
  parent: null,                                                 // 調用該模塊的模塊古徒,如果直接執(zhí)行就是null
  filename: '/path/to/demo/math.js',                            // 帶絕對路徑的文件名
  loaded: false,                                                // 模塊是否加載完成
  children: [],                                                 // 模塊的依賴
  paths:                                                        // 模塊依賴的搜索路徑
   [ '/path/to/demo/node_modules',
     '/path/to/node_modules',
     '/path/node_modules',
     '/node_modules' ] }

exports

module對象中是有字段exports的拓提,exports實際上就是module.exports

var exports = module.exports;

因此導出變量有兩種方式:

exports.area = (r) => PI * r ^ 2;
exports.circumference = (r) => 2 * PI * r;

// 或者也可以如下
module.exports.area = (r) => PI * r ^ 2;
module.exports.circumference = (r) => 2 * PI * r;

因為exportsmodule.exports的引用隧膘,在導出的時候我們就要格外小心了代态。

exports.area = (r) => PI * r ^ 2;
module.exports = (r) => 2 * PI * r; // 將module.exports對象覆蓋寺惫,area這個變量就不會被導出。

exports = (r) => 2 * PI * r; // exports就不再是module.exports的引用了蹦疑,會導致后面的circumference導出無效西雀。
exports.circumference = (r) => 2 * PI * r;

require

require的參數(shù)是模塊id,require實現(xiàn)的功能就是根據(jù)模塊id去找到對應的依賴模塊歉摧。模塊id的變數(shù)主要在兩個方面艇肴,一個是后綴名,一個是路徑叁温。

首先來說后綴名再悼,一般默認是js的,所以我們在依賴的以后一般不需要添加后綴名膝但。而且找不到的話冲九,Node.js還會嘗試添加.json.node后綴去查找跟束。

var area = require('./math').area;

// 和上面是一樣的
var area = require('./math.js').area;

再來說路徑莺奸,絕對路徑和相對路徑就不多說,比較好理解冀宴。

var area = require('/math').area;   // 在指定的絕對路徑查找模塊
var area = require('./math').area;  // 在相對與當前目錄的路徑查找模塊

還有如果不是以"."灭贷、".."或者"/"開頭的話,那就會先去核心模塊路徑找略贮,找不到再按照module.paths指定的路徑找氧腰。

var area = require('math').area;

AMD

同樣的,本節(jié)采用RequireJS來說明AMD標準刨肃。先上一個例子古拴。

// math.js
define('app/math', function () {
    const { PI } = Math;
    return {
        area: function (r) {
          return PI * r ^2;math.js
        },
        circumference: function (r) {
          return 2 * PI * r;
        }
    };
});

// main1.js
define(['app/math', 'print'], function (math, print) {
    print(math.area(3));
});

// main2.js
define(function (require) {
    var math = require('./math');
    var print = require('print');
    print(math.area(3));
});

define

AMD使用define這個api來定義一個模塊,其語法比較簡單真友。

define(id?, dependencies?, factory);

模塊id和依賴都是可選參數(shù)黄痪,只有構造函數(shù)是必須的。

id

AMD的模塊id和CommonJSmodule對象中的id作用是一樣的盔然,用來唯一的指定模塊桅打,一般是模塊的絕對路徑。雖然define函數(shù)將這個id暴露給使用者愈案,但一般也是不填的挺尾,一些優(yōu)化工具會自動生成絕對路徑作為id參數(shù)傳給define函數(shù)。id的定義也和CommonJS類似站绪,相對路徑遭铺、絕對路徑、js后綴可以省略等等。詳細的可以查看AMD模塊id的格式魂挂。

dependencies

factory函數(shù)中使用到的依賴需要先在這里指明甫题,比如示例代碼,需要指明app/mathprint涂召,然后將他們作為factory的參數(shù)傳給函數(shù)體使用坠非。AMD協(xié)議保證在factory函數(shù)執(zhí)行之前,能將所有的依賴都準備好果正。

除了指明依賴之外炎码,dependencies還有一種寫法。這種寫法是為了方便復用按照CommonJS規(guī)范寫的模塊秋泳,足見AMD規(guī)范的良苦用心辅肾。

define(function(require, exports, module) {
    var a = require("a");
    exports.foo = function () {
        return a.bar();
    };
});

RequireJS中依賴的查找路徑是通過配置文件來指定的baseUrlpaths轮锥、bundles等矫钓,這一點和Node.js是完全不一樣的。

AMD這個標準有個比較明顯的缺陷就是所有的依賴都必須要先執(zhí)行舍杜,這個從其接口的設計上就能看出來新娜。如果依賴比較多的話,這個事情就比較坑爹了既绩。

factory

這個參數(shù)名字比較有意思概龄,叫工廠函數(shù),當某塊被依賴的時候饲握,這個工廠函數(shù)就會被執(zhí)行私杜,而且即便被依賴多次,也只會執(zhí)行一次救欧。在factory中需要導出變量的時候衰粹,直接return就可以了,當然也可以使用CommonJS規(guī)范的exports笆怠。

相比較而言铝耻,AMD標準還是比較復雜的。

CMD

CMD雖然沒有CommonJSAMD出名蹬刷,但是SeaJS在國內還是比較出名瓢捉,這里也捎帶提及CMD規(guī)范,不多說办成,來demo代碼先泡态。

// math.js
define(function(require, exports, module) {
    const { PI } = Math;
    exports.area = function (r) {
        return PI * r ^2;math.js
    },
    exports.circumference = function (r) {
        return 2 * PI * r;
    }
});

// main1.js
define(function(require, exports, module) {
    var area = require('./math').area;
    var print = require('print');
    print(area(3));
});

上面的示例和AMD的示例雖然比較像,但是實際上CMD的規(guī)范和AMD還是不太一樣的迂卢,有自己的一些特色某弦。

define

模塊定義和雖然和AMD一樣用的是define函數(shù)桐汤,但是只支持factory一個參數(shù)。

define(factory);

factory和AMD也是類似的刀崖,可以是函數(shù)惊科,也可以是一個object拍摇。

require && require.async

CMD除了有同步的require接口亮钦,還有異步接口require.async,這樣就解決了我們之前提到的AMD需要先把所有依賴都加載好才能執(zhí)行factory的弊端充活。

define(function(require) {
    // 同步接口示例
    var a = require('./a');
    a.doSomething();

    // 異步接口示例
    require.async('./b', function(b) {
        b.doSomething();
    });
})

exports

這個就比較類似CommonJS的exports了蜂莉,是用來輸出API或者對象的。

module

這個也比較類似CommonJS的module對象混卵,不過相比于Node.js的module對象要簡單的多映穗,只包括

module.uri                  // 模塊完整解析出來的uri
module.dependencies         // 所有的依賴
module.exports              // 導出的能力

從上面的簡單描述可以看出,CMD想同時解決AMD和CommonJS能解決的問題幕随,基于AMD和CommonJS的設計做了簡化優(yōu)化蚁滋,同時設計了異步require的接口等。關于CMD的大量細節(jié)可以查看SeaJS官網(wǎng)赘淮。

ES6

一直以來JavaScript語言本身是沒有內置的模塊系統(tǒng)辕录,ES6終結了這個局面。雖然ES6的普及還需要好多年梢卸,但ES6完全兼容ES5的所有特性走诞。ES6的寫法可以通過轉換工具轉成ES5來執(zhí)行,是時候好好學習ES6了蛤高。

讓我們來看看用ES6實現(xiàn)上面的示例是什么樣的蚣旱?

// math.js
const { PI } = Math;
export function area(r) {
    return PI * r ^ 2;
}
export function circumference(r) {
    return 2 * PI * r;
}

// main.js
import { area, circumference } from './math';
console.log(area(3));

export

ES6的模塊是嚴格要求一個模塊一個文件,一個文件一個模塊的戴陡。每個模塊可以只導出一個變量塞绿,也可以導出多個變量。

一個模塊導出多次使用命名的導出(named exports)恤批。

export const sqrt = Math.sqrt;
export function square(x) {
    return x * x;
}

一個模塊只導出一次使用默認導出(default exports)位隶,非常方便。

export default 'abc';
export default foo();
export default /^xyz$/;
export default 5 * 7;
export default { no: false, yes: true };
export default function () {}

import

ES6的import和之前標準的require是比較不一樣的开皿,被導出變量是原有變量的只讀視圖涧黄。這意味著雖然變量被導出了,但是它還是和內部變量保持關聯(lián)赋荆,被導出變量的變化笋妥,會導致內部變量也跟著變化。也許這正是ES6重新取了import這個名字而沒有使用require的原因窄潭。這一點和require是完全不一樣的春宣,require變量導出之后就生成了一個新的變量,和原始的內部變量就脫離關系了。有個demo能比較好的說明這個問題月帝。

//------ lib.js ------
export let counter = 3;
export function incCounter() {
    counter++;
}

//------ main.js ------
import { counter, incCounter } from './lib';

// The imported value `counter` is live
console.log(counter); // 3
incCounter();
console.log(counter); // 4

模塊是ES6語言的一項重大特性躏惋,里面的細節(jié)比較多,詳細描述怕是篇幅太長了嚷辅,需要詳細了解ES6模塊語法的同學請移步ES Modules簿姨。

總結

本文簡單描述了CommonJS、AMD簸搞、CMD以及ES6的模塊標準扁位,仔細研究各個標準的細節(jié)可以一窺JavaScript模塊化標準的發(fā)展歷程。JavaScript語言早期作為網(wǎng)站的一種腳本語言趁俊,不需要模塊化這種特性域仇,但隨著node.js的出現(xiàn),js的工程越來越復雜寺擂,模塊化也越來越重要暇务。CommonJS、AMD和CMD是在語言不支持的情況下發(fā)展出來的第三方模塊化解決方案怔软,ES6正是基于這些解決方案提出了語言內置的模塊標準垦细,希望ES6能盡快的推廣起來,這樣JSer就能輕松許多啦爽雄。

參考文獻

微信一鍵關注
微信一鍵關注
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末蝠检,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子挚瘟,更是在濱河造成了極大的恐慌叹谁,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,590評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件乘盖,死亡現(xiàn)場離奇詭異焰檩,居然都是意外死亡,警方通過查閱死者的電腦和手機订框,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,157評論 3 399
  • 文/潘曉璐 我一進店門析苫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人穿扳,你說我怎么就攤上這事衩侥。” “怎么了矛物?”我有些...
    開封第一講書人閱讀 169,301評論 0 362
  • 文/不壞的土叔 我叫張陵茫死,是天一觀的道長。 經(jīng)常有香客問我履羞,道長峦萎,這世上最難降的妖魔是什么屡久? 我笑而不...
    開封第一講書人閱讀 60,078評論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮爱榔,結果婚禮上被环,老公的妹妹穿的比我還像新娘。我一直安慰自己详幽,他們只是感情好筛欢,可當我...
    茶點故事閱讀 69,082評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著妒潭,像睡著了一般悴能。 火紅的嫁衣襯著肌膚如雪揣钦。 梳的紋絲不亂的頭發(fā)上雳灾,一...
    開封第一講書人閱讀 52,682評論 1 312
  • 那天,我揣著相機與錄音冯凹,去河邊找鬼谎亩。 笑死,一個胖子當著我的面吹牛宇姚,可吹牛的內容都是我干的匈庭。 我是一名探鬼主播,決...
    沈念sama閱讀 41,155評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼浑劳,長吁一口氣:“原來是場噩夢啊……” “哼阱持!你這毒婦竟也來了?” 一聲冷哼從身側響起魔熏,我...
    開封第一講書人閱讀 40,098評論 0 277
  • 序言:老撾萬榮一對情侶失蹤衷咽,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后蒜绽,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體镶骗,經(jīng)...
    沈念sama閱讀 46,638評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,701評論 3 342
  • 正文 我和宋清朗相戀三年躲雅,在試婚紗的時候發(fā)現(xiàn)自己被綠了鼎姊。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,852評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡相赁,死狀恐怖相寇,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情钮科,我是刑警寧澤唤衫,帶...
    沈念sama閱讀 36,520評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站跺嗽,受9級特大地震影響战授,放射性物質發(fā)生泄漏页藻。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,181評論 3 335
  • 文/蒙蒙 一植兰、第九天 我趴在偏房一處隱蔽的房頂上張望份帐。 院中可真熱鬧,春花似錦楣导、人聲如沸废境。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,674評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽噩凹。三九已至,卻和暖如春毡咏,著一層夾襖步出監(jiān)牢的瞬間驮宴,已是汗流浹背阱洪。 一陣腳步聲響...
    開封第一講書人閱讀 33,788評論 1 274
  • 我被黑心中介騙來泰國打工夕土, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人赴蝇。 一個月前我還...
    沈念sama閱讀 49,279評論 3 379
  • 正文 我出身青樓恢总,卻偏偏與公主長得像迎罗,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子片仿,可洞房花燭夜當晚...
    茶點故事閱讀 45,851評論 2 361

推薦閱讀更多精彩內容