咱們知道最多見的模塊化方案有CommonJS、AMD、CMD、ES6,AMD規範通常用於瀏覽器,異步的,由於模塊加載是異步的,js解釋是同步的,因此有時候致使依賴還沒加載完畢,同步的代碼運行結束;CommonJS規範通常用於服務端,同步的,由於在服務器端全部文件都存儲在本地的硬盤上,傳輸速率快並且穩定。javascript
最開始的時候,多個script標籤引入js文件。可是,這種弊端也很明顯,不少個js文件合併起來,也是至關於一個script,形成變量污染。項目大了,不想變量污染也是很難或者不容易作到,開發和維護成本高。 並且對於標籤的順序,也是須要考慮一陣,還有加載的時候同步,更加是一種災難,幸虧後來有了渲染完執行的defer和下載完執行的async,進入新的時代了。html
接着,就有各類各樣的動態建立script標籤的方法,最終發展到了上面的幾種方案。java
異步模塊定義,提供定義模塊及異步加載該模塊依賴的機制。AMD遵循依賴前置,代碼在一旦運行到須要依賴的地方,就立刻知道依賴是什麼。而無需遍歷整個函數體找到它的依賴,所以性能有所提高。可是開發者必須先前知道依賴具體有什麼,而且顯式指明依賴,使得開發工做量變大。並且,不能保證模塊加載的時候的順序。 典型表明requirejs。require.js在聲明依賴的模塊時會馬上加載並執行模塊內的代碼。require函數讓你可以隨時去依賴一個模塊,即取得模塊的引用,從而即便模塊沒有做爲參數定義,也可以被使用。他的風格是依賴注入,好比:node
/api.js
define('myMoudle',['foo','bar'],function(foo,bar){
//引入了foo和bar,利用foo、bar來作一些事情
return {
baz:function(){return 'api'}
}
});
require(['api'],function(api) {
console.log(api.baz())
})
複製代碼
而後你能夠在中間隨時引用模塊,可是模塊第一次初始化的時間比較長。這就像開始的時候很拼搏很辛苦,到最後是美滋滋。webpack
通用模塊定義,提供模塊定義及按需執行模塊。遵循依賴就近,代碼在運行時,最開始的時候是不知道依賴的,須要遍歷全部的require關鍵字,找出後面的依賴。一個常見的作法是將function toString後,用正則匹配出require關鍵字後面的依賴。CMD 裏,每一個 API 都簡單純粹。可讓瀏覽器的模塊代碼像node同樣,由於同步因此引入的順序是能控制的。 對於典型表明seajs,通常是這樣子:git
define(function(require,exports,module){
//...不少代碼略過
var a = require('./a');
//要用到a,因而引入了a
//作一些和模塊a有關的事情
});
複製代碼
對於b.js依賴a.jsgithub
//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推崇依賴就近、延遲執行。web
AMD須要先列出清單,後面使用的時候隨便使用(依賴前置),異步,特別適合瀏覽器環境下使用(底層其實就是動態建立script標籤)。並且API 默認是一個當多個用。api
CMD不須要知道依賴是什麼,到了改須要的時候才引入,並且是同步的,就像臨時抱佛腳同樣。數組
對於客戶端的瀏覽器,一說到下載、加載,確定就是和異步脫不了關係了,註定瀏覽器通常用AMD更好了。可是,CMD的api都是有區分的,局部的require和全局的require不同。
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都只能在運行時肯定具體依賴是什麼。
通常服務端的文件都在本地的硬盤上面。對於客戶,他們用的瀏覽器是要從這裏下載文件的,在服務端通常讀取文件很是快,因此同步是不會有太大的問題。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引入的這個變量是隻讀的,試圖進行對他賦值將會報錯。
就是a依賴b,b依賴a,對於不一樣的規範也有不一樣的結果。
對於node,每個模塊的exports={done:false}表示一個模塊有沒有加載完畢,通過一系列的加載最後所有都會變爲true。 同步,從上到下,只輸出已經執行的那部分代碼 首先,咱們寫兩個js用node跑一下:
//a.js
console.log('a.js')
var b = require('./b.js')
console.log(1)
//b.js
console.log('b.js')
var a = require('./a.js')
console.log(2)
//根據他的特色,require一個文件的時候,立刻運行內部的代碼,因此至關於
console.log('a.js')
console.log('b.js')
console.log(2)
console.log(1)
//輸出是a.js、b.js、二、1
複製代碼
加上export的時候:
//a.js
module.exports = {val:1}
var b = require('./b.js')
console.log(b.val)
module.exports = {val:2}
b.val = 3
console.log(b)
//b.js
module.exports = {val:1}
var a = require('./a.js')
console.log(a.val)
module.exports = {val:2}
a.val = 3
console.log(a)
//1.在a.js暴露出去一個對象module.exports = {val:1}
//2.require了b,來到b,運行b腳本
//3.b的第一行,把{val:1}暴露出去,引入剛剛a暴露的{val:1},打印a.val的結果確定是1
//4.從新暴露一次,是{val:2},而後作了一件多餘的事情,改a.val爲3(反正是拷貝過的了怎麼改都不會影響a.js),毫無疑問打印出{ val: 3 }
//5.回到a,繼續第三行,打印b.val,由於b暴露的值是2,打印2
//6.繼續再作一件無心義的事情,打印{ val: 3 }
複製代碼
解決辦法:代碼合理拆分
ES6模塊是輸出值的引用,是動態引用,等到要用的時候才用,所以能夠完美實現相互依賴,在相互依賴的a.mjs和b.mjs,執行a的時候,當發現import立刻進入b並執行b的代碼。當在b發現了a的時候,已經知道從a輸入了接口來到b的,不會回到a。可是在使用的過程當中須要注意,變量的順序。
若是是單純的暴露一個基本數據類型,固然會報錯not defined。 由於函數聲明會變量提高,因此咱們能夠改爲函數聲明(不能用函數表達式)
//a.mjs
import b from './b'
console.log(b())
function a(){return 'a'}
export default a
//b.mjs
import a from './a'
console.log(a())
function b(){return 'b'}
export default b
複製代碼
咱們通常使用的時候,都是依賴注入,若是是有循環依賴,那麼能夠直接利用require解決
define('a',['b'],function(b){
//dosomething
});
define('b',['a'],function(a){
//dosomething
});
//爲了解決循環依賴,在循環依賴發生的時候,引入require:
define('a',['b','require'],function(b,require){
//dosomething
require('b')
});
複製代碼
循環依賴,通常就是這樣
//a.js
define(function(require, exports, module){
var b = require('./b.js');
//......
});
//b.js
define(function(require, exports, module){
var a = require('./a.js');
//......
});
複製代碼
而實際上,並無問題,由於sea本身解決了這個問題: 一個模塊有幾種狀態:
'FETCHING': 模塊正在下載中 'FETCHED': 模塊已下載 'SAVED': 模塊信息已保存 'READY': 模塊的依賴項都已下載,等待編譯 'COMPILING':模塊正在編譯中 'COMPILED': 模塊已編譯
步驟:
對於全部的模塊相互依賴的通用的辦法,將相互依賴的部分抽取出來,放在一箇中間件,利用發佈訂閱模式解決
假設咱們定義兩個js:app.js是主入口文件,a.js、b.js是app依賴文件,用的是COMMONJS規範 webpack首先會從入口模塊app.js開始,根據引入方法require把全部的模塊都讀取,而後寫在一個列表上:
var modules = {
'./b.js': generated_b,
'./a.js': generated_a,
'./app.js': generated_app
}
複製代碼
'generated_'+name是一個IIFE,每一個模塊的源代碼都在裏面,不會暴露內部的變量。好比對於沒有依賴其餘模塊的a.js通常是這樣,沒有變化:
function generated_a(module, exports, webpack_require) {
// ...a的所有代碼
}
複製代碼
對於app.js則不同了:
function generated_app(module, exports, webpack_require) {
var a_imported_module = __webpack_require__('./a.js');
var b_imported_module = __webpack_require__('./b.js');
a_imported_module['inc']();
b_imported_module['inc']();
}
複製代碼
__webpack_require__就是require、exports、import這些的具體實現,夠動態地載入模塊a、b,而且將結果返回給app
對於webpack_require,大概是這樣的流程
var installedModules = {};//保存已經加載完成的模塊
function webpack_require(moduleId) {
if (installedModules[moduleId]) {//若是已經加載完成直接返回
return installedModules[moduleId].exports;
}
var module = installedModules[moduleId] = {//若是是第一次加載,則記錄在表上
i: moduleId,
l: false,//沒有下載完成
exports: {}
};
//在模塊清單上面讀取對應的路徑所對應的文件,將模塊函數的調用對象綁定爲module.exports,並返回
modules[moduleId].call(module.exports, module, module.exports,__webpack_require__);
module.l = true;//下載完成
return module.exports;
}
複製代碼
對於webpack打包後的文件,是一個龐大的IIFE,他的內容大概是這樣子:
(function(modules) {
var installedModules = {};
function __webpack_require__(moduleId) { /*...*/}
__webpack_require__.m = modules;//全部的文件依賴列表
__webpack_require__.c = installedModules;//已經下載完成的列表
__webpack_require__.d = function(exports, name, getter) {//定義模塊對象的getter函數
if(!__webpack_require__.o(exports, name)) {
Object.defineProperty(exports, name, {
configurable: false,
enumerable: true,
get: getter
});
}
};
__webpack_require__.n = function(module) {//當和ES6模塊混用的時候的處理
var getter = module && module.__esModule ?//若是是ES6模塊用module.default
function getDefault() { return module['default']; } :
function getModuleExports() { return module; };//是COMMONJS則繼續用module
__webpack_require__.d(getter, 'a', getter);
return getter;
};
__webpack_require__.o = function(object, property) { //判斷是否有某種屬性(如exports)
return Object.prototype.hasOwnProperty.call(object, property);
};
__webpack_require__.p = "";//默認路徑爲當前
return __webpack_require__(__webpack_require__.s = 0);//讀取第一個模塊
})
/************************************************************************/
//IIFE第二個括號部分
([
(function(module, exports, __webpack_require__) {
var a = __webpack_require__(1);
var b = __webpack_require__(2);
//模塊app代碼
}),
(function(module, exports, __webpack_require__) {
//模塊a代碼
module.exports = ...
}),
(function(module, exports, __webpack_require__) {
//模塊b代碼
module.exports = ...
})
]);
複製代碼
若是是ES6模塊,處理的方法也不同。仍是假設咱們定義兩個js:app.js是主入口文件,a.js、b.js是app依賴文件。
(function(modules) {
//前面這段是同樣的
})
([
(function(module, __webpack_exports__, __webpack_require__) {//入口模塊
Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
var __WEBPACK_IMPORTED_MODULE_0__m__ = __webpack_require__(1);
var __WEBPACK_IMPORTED_MODULE_1__m__ = __webpack_require__(2);
Object(__WEBPACK_IMPORTED_MODULE_0__m__["a"])();//用object包裹着,使得其餘模塊export的內容即便是基本數據類型,也要讓他變成一個引用類型
Object(__WEBPACK_IMPORTED_MODULE_1__m__["b"])();
}),
(function(module, __webpack_exports__, __webpack_require__) {
__webpack_exports__["a"] = a;//也就是export xxx
//....
}),
(function(module, __webpack_exports__, __webpack_require__) {
__webpack_exports__["b"] = b;
//....
})
]);
複製代碼