相關推薦:
AMD、CMD、CommonJs
是ES5
中提供的模組化程式設計方案,import/export
是ES6
中新增的模組化程式設計方案。
那麼,究竟什麼什麼是AMD、CMD、CommonJs
?他們之間又存在什麼區別呢?專案開發應該選用哪種模組化程式設計規範,又是如何使用?本篇博文將一一解答以上疑問。
AMD
是」Asynchronous Module Definition
」的縮寫,即」非同步模組定義」。它採用非同步方式載入模組,模組的載入不影響它後面語句的執行。
這裡非同步指的是不堵塞瀏覽器其他任務(dom
構建,css
渲染等),而載入內部是同步的(載入完模組後立即執行回撥)。
RequireJS
:是一個AMD
框架,可以非同步載入JS
檔案,按照模組載入方法,通過define()函數定義,第一個引數是一個陣列,裡面定義一些需要依賴的包,第二個引數是一個回撥函數,通過變數來參照模組裡面的方法,最後通過return來輸出。
AMD
是RequireJS
在推廣過程中對模組定義的規範化產出,它是一個概念,RequireJS
是對這個概念的實現,就好比JavaScript
語言是對ECMAScript
規範的實現。AMD
是一個組織,RequireJS
是在這個組織下自定義的一套指令碼語言。
不同於CommonJS
,它要求兩個引數:
require([module], callback);
第一個引數[module]
,是一個陣列,裡面的成員是要載入的模組,callback
是載入完成後的回撥函數。如果將上述的程式碼改成AMD
方式:
require(['math'], function(math) { math.add(2, 3);})
其中,回撥函數中引數對應陣列中的成員(模組)。
requireJS
載入模組,採用的是AMD
規範。也就是說,模組必須按照AMD
規定的方式來寫。
具體來說,就是模組書寫必須使用特定的define()
函數來定義。如果一個模組不依賴其他模組,那麼可以直接寫在define()
函數之中。
define(id, dependencies, factory);
id
:模組的名字,如果沒有提供該引數,模組的名字應該預設為模組載入器請求的指定指令碼名字;dependencies
:模組的依賴,已被模組定義的模組標識的陣列字面量。依賴引數是可選的,如果忽略此引數,它應該預設為["require", "exports", "module"]
。然而,如果工廠方法的長度屬性小於3,載入器會選擇以函數的長度屬性指定的引數個數呼叫工廠方法。factory
:模組的工廠函數,模組初始化要執行的函數或物件。如果為函數,它應該只被執行一次。如果是物件,此物件應該為模組的輸出值。
假定現在有一個math.js
檔案,定義了一個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) { // ...})
當require()
函數載入math
模組的時候,就會先載入dependenceModule
模組。當有多個依賴時,就將所有的依賴都寫在define()
函數第一個引數陣列中,所以說AMD
是依賴前置的。這不同於CMD
規範,它是依賴就近的。
CMD
CMD
即Common Module Definition
通用模組定義,是SeaJS
在推廣過程中對模組定義的規範化產出,是一個同步模組定義,是SeaJS
的一個標準,SeaJS
是CMD
概念的一個實現,SeaJS
是淘寶團隊玉伯提供的一個模組開發的js
框架。CMD
規範是國內發展出來的,就像AMD
有個requireJS
,CMD
有個瀏覽器的實現SeaJS
,SeaJS
要解決的問題和requireJS
一樣,只不過在模組定義方式和模組載入(可以說執行、解析)時機上有所不同。
CMD
通過define()
定義,沒有依賴前置,通過require
載入jQuery
外掛,CMD
是依賴就近,在什麼地方使用到外掛就在什麼地方require
該外掛,即用即返,這是一個同步的概念。
在 CMD
規範中,一個模組就是一個檔案。程式碼的書寫格式如下:
define(function(require, exports, module) { // 模組程式碼});
其中,
require
是可以把其他模組匯入進來的一個引數;exports
是可以把模組內的一些屬性和方法匯出的;module
是一個物件,上面儲存了與當前模組相關聯的一些屬性和方法。
AMD
是依賴關係前置,在定義模組的時候就要宣告其依賴的模組;CMD
是按需載入依賴就近,只有在用到某個模組的時候再去require
,範例程式碼如下:
// CMDdefine(function(require, exports, module) { var a = require('./a') a.doSomething() // 此處略去 100 行 var b = require('./b') // 依賴可以就近書寫 b.doSomething() // ... })// AMD 預設推薦的是define(['./a', './b'], function(a, b) { // 依賴必須一開始就寫好 a.doSomething() // 此處略去 100 行 b.doSomething() ...})
CommonJS
規範是通過module.exports
定義的,在前端瀏覽器裡面並不支援module.exports
,通過node.js
後端使用。Nodejs
端使用CommonJS
規範,前端瀏覽器一般使用AMD
、CMD
、ES6
等定義模組化開發規範。
CommonJS
的終極目標是提供一個類似Python
,Ruby
和Java
的標準庫。這樣的話,開發者可以使用CommonJS API
編寫應用程式,然後這些應用就可以執行在不同的JavaScript
直譯器和不同的主機環境中。
在相容CommonJS
的系統中,你可以使用JavaScript
開發以下程式:
- 伺服器端
JavaScript
應用程式;- 命令列工具;
- 圖形介面應用程式;
- 混合應用程式(如,Titanium或Adobe AIR);
2009年,美國程式設計師Ryan Dahl創造了node.js
專案,將javascript
語言用於伺服器端程式設計。這標誌"Javascript
模組化程式設計"正式誕生。NodeJS
是CommonJS
規範的實現,webpack
也是以CommonJS
的形式來書寫。
node.js
的模組系統,就是參照CommonJS
規範實現的。在CommonJS
中,有一個全域性性方法require()
,用於載入模組。假定有一個數學模組math.js
,就可以像下面這樣載入。
var math = require('math');
然後,就可以呼叫模組提供的方法:
var math = require('math');math.add(2,3); // 5
CommonJS
定義的模組分為:模組參照(require)、 模組定義(exports)、模組標識(module)。
其中,
require()
用來引入外部模組;exports
物件用於匯出當前模組的方法或變數,唯一的匯出口;module
物件就代表模組本身。
雖說NodeJS
遵循CommonJS
的規範,但是相比也是做了一些取捨,添了一些新東西的。
NPM
作為Node
包管理器,同樣遵循CommonJS
規範。
下面講講commonJS
的原理以及簡易實現:
1、原理
瀏覽器不相容CommonJS
的根本原因,在於缺少四個Node.js
環境變數。
module exports require global
只要能夠提供這四個變數,瀏覽器就能載入 CommonJS
模組。
下面是一個簡單的範例。
var module = { exports: {}};(function(module, exports) { exports.multiply = function (n) { return n * 1000 }; }(module, module.exports))var f = module.exports.multiply; f(5) // 5000
上面程式碼向一個立即執行函數提供 module 和 exports 兩個外部變數,模組就放在這個立即執行函數裡面。模組的輸出值放在 module.exports 之中,這樣就實現了模組的載入。
2、Browserify 的實現Browserify
是目前最常用的 CommonJS
格式轉換工具。
請看一個例子,main.js
模組載入 foo.js
模組。
// foo.jsmodule.exports = function(x) { console.log(x);};// main.jsvar foo = require("./foo");foo("Hi");
使用下面的命令,就能將main.js
轉為瀏覽器可用的格式。
$ browserify main.js > compiled.js
其中,Browserify
到底做了什麼?安裝一下browser-unpack
,就清楚了。
$ npm install browser-unpack -g
然後,將前面生成的compile.js解包。
$ browser-unpack < compiled.js
[ { "id":1, "source":"module.exports = function(x) {\n console.log(x);\n};", "deps":{} }, { "id":2, "source":"var foo = require(\"./foo\");\nfoo(\"Hi\");", "deps":{"./foo":1}, "entry":true }]
可以看到,browerify
將所有模組放入一個陣列,id
屬性是模組的編號,source
屬性是模組的原始碼,deps
屬性是模組的依賴。
因為 main.js
裡面載入了 foo.js
,所以 deps
屬性就指定 ./foo
對應1號模組。執行的時候,瀏覽器遇到 require('./foo')
語句,就自動執行1號模組的 source
屬性,並將執行後的 module.exports
屬性值輸出。
有關es6
模組特性,強烈推薦阮一峰老師的:ECMAScript 6 入門 - Module 的語法專欄。
要說 ES6
模組特性,那麼就先說說 ES6
模組跟 CommonJS
模組的不同之處。
ES6
模組輸出的是值的參照,輸出介面動態繫結,而CommonJS
輸出的是值的拷貝;ES6
模組編譯時執行,而CommonJS
模組總是在執行時載入。
CommonJS
模組輸出的是值的拷貝(原始值的拷貝),也就是說,一旦輸出一個值,模組內部的變化就影響不到這個值。
// a.jsvar b = require('./b');console.log(b.foo);setTimeout(() => { console.log(b.foo); console.log(require('./b').foo);}, 1000);// b.jslet foo = 1;setTimeout(() => { foo = 2;}, 500);module.exports = { foo: foo,};// 執行:node a.js// 執行結果:// 1// 1// 1
上面程式碼說明,b 模組載入以後,它的內部 foo 變化就影響不到輸出的 exports.foo 了。這是因為 foo 是一個原始型別的值,會被快取。所以如果你想要在 CommonJS
中動態獲取模組中的值,那麼就需要藉助於函數延時執行的特性。
// a.jsvar b = require('./b');console.log(b.foo);setTimeout(() => { console.log(b.foo); console.log(require('./b').foo);}, 1000);// b.jsmodule.exports.foo = 1; // 同 exports.foo = 1 setTimeout(() => { module.exports.foo = 2;}, 500);// 執行:node a.js// 執行結果:// 1// 2// 2
所以我們可以總結一下:
CommonJS
模組重複引入的模組並不會重複執行,再次獲取模組直接獲得暴露的module.exports
物件。- 如果你需要處處獲取到模組內的最新值的話,也可以每次更新資料的時候每次都要去更新
module.exports
上的值- 如果暴露的
module.exports
的屬性是個物件,那就不存在這個問題了。
相關推薦:
以上就是JavaScript模組化程式設計規範之CommonJS、AMD、CMD、ES6的詳細內容,更多請關注TW511.COM其它相關文章!