好程序員web前端教程分享js中的模塊化一:咱們知道最多見的模塊化方案有CommonJS、AMD、CMD、ES6,AMD規範通常用於瀏覽器,異步的,由於模塊加載是異步的,js解釋是同步的,因此有時候致使依賴還沒加載完畢,同步的代碼運行結束;CommonJS規範通常用於服務端,同步的,由於在服務器端全部文件都存儲在本地的硬盤上,傳輸速率快並且穩定。前端
1.script標籤引入node
最開始的時候,多個script標籤引入js文件。可是,這種弊端也很明顯,不少個js文件合併起來,也是至關於一個script,形成變量污染。項目大了,不想變量污染也是很難或者不容易作到,開發和維護成本高。 並且對於標籤的順序,也是須要考慮一陣,還有加載的時候同步,更加是一種災難,幸虧後來有了渲染完執行的defer和下載完執行的async,進入新的時代了。程序員
接着,就有各類各樣的動態建立script標籤的方法,最終發展到了上面的幾種方案。web
2.AMD與CMDapi
2.1AMD數組
異步模塊定義,提供定義模塊及異步加載該模塊依賴的機制。AMD遵循依賴前置,代碼在一旦運行到須要依賴的地方,就立刻知道依賴是什麼。而無需遍歷整個函數體找到它的依賴,所以性能有所提高。可是開發者必須先前知道依賴具體有什麼,而且顯式指明依賴,使得開發工做量變大。並且,不能保證模塊加載的時候的順序。 典型表明requirejs。require.js在聲明依賴的模塊時會馬上加載並執行模塊內的代碼。require函數讓你可以隨時去依賴一個模塊,即取得模塊的引用,從而即便模塊沒有做爲參數定義,也可以被使用。他的風格是依賴注入,好比:瀏覽器
/api.js緩存
define('myMoudle',['foo','bar'],function(foo,bar){服務器
//引入了foo和bar,利用foo、bar來作一些事情 return { baz:function(){return 'api'} }
});app
require(['api'],function(api) {
console.log(api.baz())
})
複製代碼
而後你能夠在中間隨時引用模塊,可是模塊第一次初始化的時間比較長。這就像開始的時候很拼搏很辛苦,到最後是美滋滋。
2.2CMD
通用模塊定義,提供模塊定義及按需執行模塊。遵循依賴就近,代碼在運行時,最開始的時候是不知道依賴的,須要遍歷全部的require關鍵字,找出後面的依賴。一個常見的作法是將function toString後,用正則匹配出require關鍵字後面的依賴。CMD 裏,每一個 API 都簡單純粹。可讓瀏覽器的模塊代碼像node同樣,由於同步因此引入的順序是能控制的。 對於典型表明seajs,通常是這樣子:
define(function(require,exports,module){
//...不少代碼略過 var a = require('./a'); //要用到a,因而引入了a //作一些和模塊a有關的事情
});
複製代碼
對於b.js依賴a.js
//a.js
define(function(require, exports) {
exports.a = function(){//也能夠把他暴露出去 // 不少代碼 };
});
//b.js
define(function(require,exports){
//前面幹了不少事情,忽然想要引用a了 var fun = require('./a');
console.log(fun.a()); // 就能夠調用到及執行a函數了。
})
//或者能夠use
seajs.use(['a.js'], function(a){
//作一些事情
});
複製代碼
AMD和CMD對比: AMD 推崇依賴前置、提早執行,CMD推崇依賴就近、延遲執行。
AMD須要先列出清單,後面使用的時候隨便使用(依賴前置),異步,特別適合瀏覽器環境下使用(底層其實就是動態建立script標籤)。並且API 默認是一個當多個用。
CMD不須要知道依賴是什麼,到了改須要的時候才引入,並且是同步的,就像臨時抱佛腳同樣。
對於客戶端的瀏覽器,一說到下載、加載,確定就是和異步脫不了關係了,註定瀏覽器通常用AMD更好了。可是,CMD的api都是有區分的,局部的require和全局的require不同。
3.CommonJS與ES6
3.1 ES6
ES6模塊的script標籤有點不一樣,須要加上type='module'
<script src='./a.js' type='module'>...</script>
複製代碼
對於這種標籤都是異步加載,並且是至關於帶上defer屬性的script標籤,不會阻塞頁面,渲染完執行。可是你也能夠手動加上defer或者async,實現指望的效果。 ES6模塊的文件後綴是mjs,經過import引入和export導出。咱們通常是這樣子:
//a.mjs
import b from 'b.js'
//b.mjs
export default b
複製代碼
ES6畢竟是ES6,模塊內自帶嚴格模式,並且只在自身做用域內運行。在ES6模塊內引入其餘模塊就要用import引入,暴露也要用export暴露。另外,一個模塊只會被執行一次。 import是ES6新語法,可靜態分析,提早編譯。他最終會被js引擎編譯,也就是能夠實現編譯後就引入了模塊,因此ES6模塊加載是靜態化的,能夠在編譯的時候肯定模塊的依賴關係以及輸入輸出的變量。ES6能夠作到編譯前分析,而CMD和AMD都只能在運行時肯定具體依賴是什麼。
3.2CommonJS
通常服務端的文件都在本地的硬盤上面。對於客戶,他們用的瀏覽器是要從這裏下載文件的,在服務端通常讀取文件很是快,因此同步是不會有太大的問題。require的時候,立刻將require的文件代碼運行
表明就是nodejs了。用得最多的,大概就是:
//app.js
var route = require('./route.js')//讀取控制路由的js文件
//route.js
var route = {......}
module.exports = route
複製代碼
require 第一次加載腳本就會立刻執行腳本,生成一個對象
區別: CommonJS運行時加載,輸出的是值的拷貝,是一個對象(都是由module.export暴露出去的),能夠直接拿去用了,不用再回頭找。因此,當module.export的源文件裏面一些原始類型值發生變化,require這邊不會隨着這個變化而變化的,由於被緩存了。可是有一種常規的操做,寫一個返回那個值的函數。就像angular裏面$watch數組裏面的每個對象,舊值是直接寫死,新值是寫一個返回新值的函數,這樣子就不會寫死。module.export輸出一個取值的函數,調用的時候就能夠拿到變化的值。
ES6是編譯時輸出接口,輸出的是值的引用,對外的接口只是一種靜態的概念,在靜態解釋後已經造成。當腳本運行時,根據這個引用去本來的模塊內取值。因此不存在緩存的狀況,import的文件變了,誰發出import的也是拿到這個變的值。模塊裏面的變量綁定着他所在的模塊。另外,經過import引入的這個變量是隻讀的,試圖進行對他賦值將會報錯。