在JS沒有模塊化標準的時代,若是存在如下依賴關係:javascript
main.js -> b.js -> a.jshtml
那麼咱們必須把js文件的順序按照模塊的依賴關係順序放到頁面中(簡單的舉例,不考慮循環依賴等複雜狀況)前端
<!-- NoModule.html --> <head> <link rel="icon" href="data:image/ico;base64,aWNv"> <script src="./a.js"></script> <script src="./b.js"></script> <script src="./main.js"></script> </head> <body></body>
咱們須要提早加載好全部的依賴。java
//main.js (function(){ moduleB.logb(); })() //b.js var moduleB = (function () { function logb() { moduleA.loga(); console.log("logb"); } return { logb: logb } })() //a.js var moduleA = (function () { function loga() { console.log("loga"); } return { loga: loga } })() //輸出結果 //loga //logb
這種方式至關簡單粗暴啊,固然形成的問題也不少:依賴關係沒法顯式維護,全局命名空間污染衝突等等node
首先:AMD是一種規範,全稱Asynchronous Module Definition 異步模塊定義webpack
其次:RequireJS(2.3.6)是AMD的一個實現,咱們可使用RequireJS來實際看看這種規範到底怎麼回事es6
依賴關係:main.js -> b.js -> a.jsweb
咱們來看看js文件的在頁面中的結構:segmentfault
<!-- AMD.html --> <head> <link rel="icon" href="data:image/ico;base64,aWNv"> <script src="./require.js"></script> <script src="./main.js"></script> </head> <body></body>
而後是各個文件的代碼:瀏覽器
//main.js console.log("load main.js"); require(['./b.js'], function (b) { console.log("call b.logb()"); b.logb(); return {}; }) console.log("end main.js"); //b.js define(['./a.js'], function (a) { console.log("load b.js"); function sleep(d) { for (var t = Date.now(); Date.now() - t <= d;); } function logb() { a.loga(); //注意,這裏暫停了5秒 var startTime = new Date().getMinutes() + ":" + new Date().getSeconds(); console.log(startTime); sleep(5000); var endTime = new Date().getMinutes() + ":" + new Date().getSeconds(); console.log(endTime); console.log("logb"); } return { logb: logb }; }) //a.js define([], function () { console.log("load a.js") function loga() { console.log("loga"); } return { loga: loga }; })
從上面能夠看出來,咱們初始頁面並不須要引入依賴的模塊js文件。Chrome中打開AMD.html,咱們能夠觀察到網絡時序圖以下,能夠明顯的發現b.js和a.js是在main.js以後被請求的。
此時再看看咱們的頁面,發現多了2個script標籤把b.js和a.js給引入進來了。
<!-- AMD.html --> <html> <head> <link rel="icon" href="data:image/ico;base64,aWNv"> <script src="./require.js"></script> <script src="./main.js"></script> <script type="text/javascript" charset="utf-8" async="" data-requirecontext="_" data-requiremodule="b.js" src="b.js"></script> <script type="text/javascript" charset="utf-8" async="" data-requirecontext="_" data-requiremodule="a.js" src="a.js"></script> </head> <body></body> </html>
這就是RequireJS幫咱們作的事情了,根據咱們指定的依賴,在代碼運行時動態的將依賴的模塊js文件加載到運行環境中。
咱們再來看看輸出:
能夠很明顯的發現,依賴模塊的加載沒有阻塞後面代碼的執行,而且模塊會在使用前加載好。
並且模塊加載是異步的。
首先:CMD是一種規範,全稱Common Module Definition 通用模塊定義
其次:Sea.js(3.0.0)是CMD的一個實現,咱們可使用Sea.js來實際看看這種規範到底怎麼回事
<!-- CMD.html --> <head> <link rel="icon" href="data:image/ico;base64,aWNv"> <script src="./sea.js"></script> <script> seajs.use("./main.js"); </script> </head> <body></body>
//main.js console.log("load main.js"); define(function (require, exports, module) { console.log("call b.logb()"); var b = require('./b.js'); b.logb(); }); console.log("end main.js"); //b.js console.log("load b.js"); define(function (require, exports, module) { function sleep(d) { for (var t = Date.now(); Date.now() - t <= d;); } function logb() { var a = require('./a.js'); a.loga(); //注意,這裏暫停了5秒 var startTime = new Date().getMinutes() + ":" + new Date().getSeconds(); console.log(startTime); sleep(5000); var endTime = new Date().getMinutes() + ":" + new Date().getSeconds(); console.log(endTime); console.log("logb"); } exports.logb = logb; }) //a.js console.log("load a.js"); define(function (require, exports, module) { function loga() { console.log("loga"); } exports.loga = loga; })
一樣的,sea.js會幫咱們把須要的依賴模塊動態的加載進來,這裏就不截圖了。
一樣的,咱們先看輸出結果:
有沒有發現,雖然寫法上依賴就近,但實際上依賴的模塊仍是被前置加載了。
最新版本中模塊加載也是異步的了。
NodeJS運行環境下的模塊規範
//main.js console.log("load main.js"); const a = require('./a.js'); const b = require('./b.js'); a.loga(); b.logb(); console.log("end main.js"); //a.js console.log("load a.js"); function loga() { console.log("loga"); } module.exports.loga = loga; //b.js console.log("load b.js"); function sleep(d) { for (var t = Date.now(); Date.now() - t <= d;); } function logb() { //注意,這裏暫停了5秒 var startTime = new Date().getMinutes() + ":" + new Date().getSeconds(); console.log(startTime); sleep(5000); var endTime = new Date().getMinutes() + ":" + new Date().getSeconds(); console.log(endTime); console.log("logb"); } exports.logb = logb;
不一樣於最新的requireJS和sea.js,CommonJS在node環境中是同步IO,會阻塞後面的代碼執行。
ES6也有本身的模塊化方案,如今咱們即便不使用AMD或者CMD的js實現庫,也能在瀏覽器中直接使用模塊化的方案了。瀏覽器的支持率能夠參考: https://caniuse.com/?search=import
<!-- ES6.html --> <head> <link rel="icon" href="data:image/ico;base64,aWNv"> <script type="module" src="./main.js"></script> <!-- <script src="./main2.js"></script> --> </head> <body>ES6.html</body>
ES6支持二種方式的模塊使用,第一種是在script上使用type=module
//main.js console.log("load main.js"); import { loga } from './a.js'; import logb from './b.js'; loga(); logb(); console.log("end main.js"); //a.js console.log("load a.js"); export function loga() { console.log("loga"); } //b.js console.log("load b.js"); function sleep(d) { for (var t = Date.now(); Date.now() - t <= d;); } function logb() { //注意,這裏暫停了5秒 var startTime = new Date().getMinutes() + ":" + new Date().getSeconds(); console.log(startTime); sleep(5000); var endTime = new Date().getMinutes() + ":" + new Date().getSeconds(); console.log(endTime); console.log("logb"); } export default { logb };
輸出結果:
能夠發現依賴模塊仍是會被提早加載,再看看第二種方式:
<!-- ES6.html --> <head> <link rel="icon" href="data:image/ico;base64,aWNv"> <!-- <script type="module" src="./main.js"></script> --> <script src="./main2.js"></script> </head> <body>ES6.html</body>
console.log("load main.js"); import('./a.js').then(a => { a.loga(); }) import('./b.js').then(b => { console.log(b.default()); }) console.log("end main.js");
結果以下:
能夠發現,模塊是異步加載進來的。
可能有人有疑問,咱們在Webpack中好像既可使用require和module.exports的CommonJS語法,也可使用export和import的ES6語法。那Webpack又是怎麼處理的?
並且,前面列出的幾個模塊化方案中基本都是一個js文件做爲一個模塊,可是好像Webpack沒有輸出那麼多的文件啊?
其實Webpack有本身的模塊化實現,兼容了這二種標準,並且還有一個編譯的過程將多文件bundle到一塊兒。詳細的能夠參考:http://www.javashuo.com/article/p-glyxitrb-q.html
其核心仍是模塊化設計的幾個要點:
其實從我的觀點來看,前端的模塊化經歷了:
這麼幾個以上的階段後,現階段基本比較穩定在預編譯模式,結合預編譯工具的其餘功能和帶來的便利,前端模塊化再也不是一個主要關注的技術點。取而代之的是更加關注:代碼分割、按需加載、Tree Shaking、模塊合併、模塊緩存等等問題。