zoukankan      html  css  js  c++  java
  • 前端模块化

    1. 没有模块化的时代

    在JS没有模块化标准的时代,如果存在以下依赖关系:

    main.js -> b.js -> a.js

    那么我们必须把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>
    

    我们需要提前加载好所有的依赖。

    //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
    

    这种方式相当简单粗暴啊,当然造成的问题也很多:依赖关系无法显式维护,全局命名空间污染冲突等等

    2. AMD

    首先:AMD是一种规范,全称Asynchronous Module Definition 异步模块定义

    其次:RequireJS(2.3.6)是AMD的一个实现,我们可以使用RequireJS来实际看看这种规范到底怎么回事

    依赖关系:main.js -> b.js -> a.js

    我们来看看js文件的在页面中的结构:

    <!-- 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到一起。详细的可以参考:https://segmentfault.com/a/1190000010349749

    其核心还是模块化设计的几个要点:

    • 模块加载
    • 模块隔离
    • 模块缓存控制
    • 模块依赖维护

    总结

    其实从个人观点来看,前端的模块化经历了:

    1. 野蛮发展阶段:每个团队和公司有自己的方案,好苦逼
    2. 到AMD/CMD阶段:行业领头人推广,大家围观
    3. 再到原生ES6支持阶段:建立浏览器标准,大家围观
    4. 和编译支持阶段:在前端越来越复杂,引入预编译模式,大家膜拜

    这么几个以上的阶段后,现阶段基本比较稳定在预编译模式,结合预编译工具的其他功能和带来的便利,前端模块化不再是一个主要关注的技术点。取而代之的是更加关注:代码分割、按需加载、Tree Shaking、模块合并、模块缓存等等问题。

  • 相关阅读:
    ftrace 使用方法
    在Ubuntu custom kernel上裝perf by compile
    [fw]How to use DISM to install a hotfix from within Windows
    Ubuntu下配置了ssh,但是连接很慢
    Fix invisible cursor issue in Ubuntu 13.10
    超強的Linux指令解釋網站《explainshell.com》,學Linux必備!
    python 读取 log日志的编码问题
    随机森林random forest及python实现
    评分卡系列(三):分类学习器的评估
    评分卡系列(二):特征工程
  • 原文地址:https://www.cnblogs.com/full-stack-engineer/p/14249312.html
Copyright © 2011-2022 走看看