前端模塊化

1. 沒有模塊化的時代

在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

2. AMD

首先: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文件加載到運行環境中。

咱們再來看看輸出:

能夠很明顯的發現,依賴模塊的加載沒有阻塞後面代碼的執行,而且模塊會在使用前加載好

並且模塊加載是異步的。

3. CMD

首先: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會幫咱們把須要的依賴模塊動態的加載進來,這裏就不截圖了。

一樣的,咱們先看輸出結果:

有沒有發現,雖然寫法上依賴就近,但實際上依賴的模塊仍是被前置加載了

最新版本中模塊加載也是異步的了。

4. CommonJS

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,會阻塞後面的代碼執行。

5. ES6 模塊

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");

結果以下:

能夠發現,模塊是異步加載進來的。

6. Webpack中的模塊化

可能有人有疑問,咱們在Webpack中好像既可使用require和module.exports的CommonJS語法,也可使用export和import的ES6語法。那Webpack又是怎麼處理的?

並且,前面列出的幾個模塊化方案中基本都是一個js文件做爲一個模塊,可是好像Webpack沒有輸出那麼多的文件啊?

其實Webpack有本身的模塊化實現,兼容了這二種標準,並且還有一個編譯的過程將多文件bundle到一塊兒。詳細的能夠參考:http://www.javashuo.com/article/p-glyxitrb-q.html

其核心仍是模塊化設計的幾個要點:

  • 模塊加載
  • 模塊隔離
  • 模塊緩存控制
  • 模塊依賴維護

總結

其實從我的觀點來看,前端的模塊化經歷了:

  1. 野蠻發展階段:每一個團隊和公司有本身的方案,好苦逼
  2. 到AMD/CMD階段:行業領頭人推廣,你們圍觀
  3. 再到原生ES6支持階段:創建瀏覽器標準,你們圍觀
  4. 和編譯支持階段:在前端愈來愈複雜,引入預編譯模式,你們膜拜

這麼幾個以上的階段後,現階段基本比較穩定在預編譯模式,結合預編譯工具的其餘功能和帶來的便利,前端模塊化再也不是一個主要關注的技術點。取而代之的是更加關注:代碼分割、按需加載、Tree Shaking、模塊合併、模塊緩存等等問題。

相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息