這是一篇關(guān)于js模塊化編程的總結(jié)記錄
隨著網(wǎng)站逐漸變成”互聯(lián)網(wǎng)應(yīng)用程序”,嵌入網(wǎng)頁的javascript代碼越來越龐大,越來越復(fù)雜。
網(wǎng)頁越來越像桌面程序,需要一個(gè)團(tuán)隊(duì)分工協(xié)作、進(jìn)度管理、單元測試等等……開發(fā)者不得不使用軟件工程的方法,管理網(wǎng)頁的業(yè)務(wù)邏輯。
Javascript模塊化編程,已經(jīng)成為一個(gè)迫切的需求。理想情況下,開發(fā)者只需要實(shí)現(xiàn)核心的業(yè)務(wù)邏輯,其他都可以加載別人已經(jīng)寫好的模塊。
Javascript社區(qū)做了很多努力,在現(xiàn)有的運(yùn)行環(huán)境中,實(shí)現(xiàn)”模塊”的效果。
這里的CommonJS規(guī)范指的是CommonJS Modules/1.0規(guī)范。
CommonJS是一個(gè)更偏向于服務(wù)器端的規(guī)范。NodeJS采用了這個(gè)規(guī)范。CommonJS的一個(gè)模塊就是一個(gè)腳本文件。require命令第一次加載該腳本時(shí)就會(huì)執(zhí)行整個(gè)腳本,然后在內(nèi)存中生成一個(gè)對(duì)象。
{ id: '...', exports: { ... }, loaded: true, ...}id是模塊名,exports是該模塊導(dǎo)出的接口,loaded表示模塊是否加載完畢。此外還有很多屬性,這里省略了。
以后需要用到這個(gè)模塊時(shí),就會(huì)到exports屬性上取值。即使再次執(zhí)行require命令,也不會(huì)再次執(zhí)行該模塊,而是到緩存中取值。
// math.jsexports.add = function(a, b) { return a + b;}var math = require('math');math.add(2, 3); // 5由于CommonJS是同步加載模塊,這對(duì)于服務(wù)器端不是一個(gè)問題,因?yàn)樗械哪K都放在本地硬盤。等待模塊時(shí)間就是硬盤讀取文件時(shí)間,很小。但是,對(duì)于瀏覽器而言,它需要從服務(wù)器加載模塊,涉及到網(wǎng)速,代理等原因,一旦等待時(shí)間過長,瀏覽器處于”假死”狀態(tài)。
所以在瀏覽器端,不適合于CommonJS規(guī)范。所以在瀏覽器端又出現(xiàn)了一個(gè)規(guī)范—-AMD。
CommonJS解決了模塊化的問題,但這種同步加載方式并不適合于瀏覽器端。
AMD是”Asynchronous Module Definition”的縮寫,即”異步模塊定義”。它采用異步方式加載模塊,模塊的加載不影響它后面語句的運(yùn)行。 這里異步指的是不堵塞瀏覽器其他任務(wù)(dom構(gòu)建,CSS渲染等),而加載內(nèi)部是同步的(加載完模塊后立即執(zhí)行回調(diào))。
AMD也采用require命令加載模塊,但是不同于CommonJS,它要求兩個(gè)參數(shù):
require([module], callback);第一個(gè)參數(shù)[module],是一個(gè)數(shù)組,里面的成員是要加載的模塊,callback是加載完成后的回調(diào)函數(shù)。如果將上述的代碼改成AMD方式:
require(['math'], function(math) { math.add(2, 3);})其中,回調(diào)函數(shù)中參數(shù)對(duì)應(yīng)數(shù)組中的成員(模塊)。
requireJS加載模塊,采用的是AMD規(guī)范。也就是說,模塊必須按照AMD規(guī)定的方式來寫。
具體來說,就是模塊書寫必須使用特定的define()函數(shù)來定義。如果一個(gè)模塊不依賴其他模塊,那么可以直接寫在define()函數(shù)之中。
define(id?, dependencies?, factory);id:模塊的名字,如果沒有提供該參數(shù),模塊的名字應(yīng)該默認(rèn)為模塊加載器請(qǐng)求的指定腳本的名字;
dependencies:模塊的依賴,已被模塊定義的模塊標(biāo)識(shí)的數(shù)組字面量。依賴參數(shù)是可選的,如果忽略此參數(shù),它應(yīng)該默認(rèn)為 ["require", "exports", "module"]
。然而,如果工廠方法的長度屬性小于3,加載器會(huì)選擇以函數(shù)的長度屬性指定的參數(shù)個(gè)數(shù)調(diào)用工廠方法。
factory:模塊的工廠函數(shù),模塊初始化要執(zhí)行的函數(shù)或?qū)ο蟆H绻麨楹瘮?shù),它應(yīng)該只被執(zhí)行一次。如果是對(duì)象,此對(duì)象應(yīng)該為模塊的輸出值。
假定現(xiàn)在有一個(gè)math.js文件,定義了一個(gè)math模塊。那么,math.js書寫方式如下:
// math.jsdefine(function() { var add = function(x, y) { return x + y; } return { add: add }})加載方法如下:
// main.jsrequire(['math'], function(math) { alert(math.add(1, 1));})如果math模塊還依賴其他模塊,寫法如下:
// math.jsdefine(['dependenceModule'], function(dependenceModule) { // ...})當(dāng)require()函數(shù)加載math模塊的時(shí)候,就會(huì)先加載dependenceModule模塊。當(dāng)有多個(gè)依賴時(shí),就將所有的依賴都寫在define()函數(shù)第一個(gè)參數(shù)數(shù)組中,所以說AMD是依賴前置的。這不同于CMD規(guī)范,它是依賴就近的。
CMD推崇依賴就近,延遲執(zhí)行??梢园涯愕囊蕾噷戇M(jìn)代碼的任意一行,如下:
define(factory)factory
為函數(shù)時(shí),表示是模塊的構(gòu)造方法。執(zhí)行該構(gòu)造方法,可以得到模塊向外提供的接口。factory 方法在執(zhí)行時(shí),默認(rèn)會(huì)傳入三個(gè)參數(shù):require、exports 和 module.
如果使用AMD寫法,如下:
// AMDdefine(['a', 'b'], function(a, b) { a.doSomething(); b.doSomething();})這個(gè)規(guī)范實(shí)際上是為了Seajs的推廣然后搞出來的。那么看看SeaJS是怎么回事兒吧,基本就是知道這個(gè)規(guī)范了。
同樣Seajs也是預(yù)加載依賴js跟AMD的規(guī)范在預(yù)加載這一點(diǎn)上是相同的,明顯不同的地方是調(diào)用,和聲明依賴的地方。AMD和CMD都是用difine和require,但是CMD標(biāo)準(zhǔn)傾向于在使用過程中提出依賴,就是不管代碼寫到哪突然發(fā)現(xiàn)需要依賴另一個(gè)模塊,那就在當(dāng)前代碼用require引入就可以了,規(guī)范會(huì)幫你搞定預(yù)加載,你隨便寫就可以了。但是AMD標(biāo)準(zhǔn)讓你必須提前在頭部依賴參數(shù)部分寫好(沒有寫好? 倒回去寫好咯)。這就是最明顯的區(qū)別。
sea.js通過sea.use()
來加載模塊。
由于CommonJS是服務(wù)器端的規(guī)范,跟AMD、CMD兩個(gè)標(biāo)準(zhǔn)實(shí)際不沖突。
當(dāng)我們寫一個(gè)文件需要兼容不同的加載規(guī)范的時(shí)候怎么辦呢,看看下面的代碼。
(function (root, factory) { if (typeof define === 'function' && define.amd) { // AMD define(['jquery', 'underscore'], factory); } else if (typeof exports === 'object') { // Node, CommonJS之類的 module.exports = factory(require('jquery'), require('underscore')); } else { // 瀏覽器全局變量(root 即 window) root.returnExports = factory(root.jQuery, root._); }}(this, function ($, _) { // 方法 function a(){}; // 私有方法,因?yàn)樗鼪]被返回 (見下面) function b(){}; // 公共方法,因?yàn)楸环祷亓? function c(){}; // 公共方法,因?yàn)楸环祷亓? // 暴露公共方法 return { b: b, c: c }}));這個(gè)代碼可以兼容各種加載規(guī)范了。
es6通過import
、export
實(shí)現(xiàn)模塊的輸入輸出。其中import命令用于輸入其他模塊提供的功能,export命令用于規(guī)定模塊的對(duì)外接口。
一個(gè)模塊就是一個(gè)獨(dú)立的文件。該文件內(nèi)部的所有變量,外部無法獲取。如果希望外部文件能夠讀取該模塊的變量,就需要在這個(gè)模塊內(nèi)使用export關(guān)鍵字導(dǎo)出變量。如:
// PRofile.jsexport var a = 1;export var b = 2;export var c = 3;下面的寫法是等價(jià)的,這種方式更加清晰(在底部一眼能看出導(dǎo)出了哪些變量):
var a = 1;var b = 2;var c = 3;export {a, b, c}export命令除了輸出變量,還可以導(dǎo)出函數(shù)或類。
導(dǎo)出函數(shù)export function foo(){}function foo(){}function bar(){}export {foo, bar as bar2}其中上面的as表示給導(dǎo)出的變量重命名。
要注意的是,export導(dǎo)出的變量只能位于文件的頂層,如果處于塊級(jí)作用域內(nèi),會(huì)報(bào)錯(cuò)。如:
function foo() { export 'bar'; // SyntaxError}導(dǎo)出類export default class {} // 關(guān)于default下面會(huì)說export語句輸出的值是動(dòng)態(tài)綁定,綁定其所在的模塊。
// foo.jsexport var foo = 'foo';setTimeout(function() { foo = 'foo2';}, 500);// main.jsimport * as m from './foo';console.log(m.foo); // foosetTimeout(() => console.log(m.foo), 500); // foo2import命令可以導(dǎo)入其他模塊通過export導(dǎo)出的部分。
// abc.jsvar a = 1;var b = 2;var c = 3;export {a, b, c}//main.jsimport {a, b, c} from './abc';console.log(a, b, c);如果想為導(dǎo)入的變量重新取一個(gè)名字,使用as關(guān)鍵字(也可以在導(dǎo)出中使用)。
import {a as aa, b, c};console.log(aa, b, c)如果想在一個(gè)模塊中先輸入后輸出一個(gè)模塊,import語句可以和export語句寫在一起。
import {a, b, c} form './abc';export {a, b, c}// 使用連寫, 可讀性不好,不建議export {a, b, c} from './abc';使用*關(guān)鍵字。
// abc.jsexport var a = 1;export var b = 2;export var c = 3;// main.jsimport * from as abc form './abc';console.log(abc.a, abc.b, abc.c);在export輸出內(nèi)容時(shí),如果同時(shí)輸出多個(gè)變量,需要使用大括號(hào){}
,同時(shí)導(dǎo)入也需要大括號(hào)。使用export defalut
輸出時(shí),不需要大括號(hào),而輸入(import)export default
輸出的變量時(shí),不需要大括號(hào)。
本質(zhì)上,export default
輸出的是一個(gè)叫做default的變量或方法,輸入這個(gè)default變量時(shí)不需要大括號(hào)。
就到這里了吧。關(guān)于循環(huán)加載(模塊相互依賴)沒寫,CommonJS和ES6處理方式不一樣。
該如何理解AMD ,CMD,CommonJS規(guī)范–javascript模塊化加載學(xué)習(xí)總結(jié)
AMD/CMD與前端規(guī)范
前端模塊化之旅(二):CommonJS、AMD和CMD
研究一下javascript的模塊規(guī)范(CommonJs/AMD/CMD)
Javascript模塊化編程(一):模塊的寫法
Javascript模塊化編程(二):AMD規(guī)范
Javascript模塊化編程(三):require.js的用法
Module
新聞熱點(diǎn)
疑難解答
圖片精選