课后作业——Js模块化

269 阅读4分钟

一、无模块化

1、开始需要在页面中增加一些不同的js

2.多种js文件被分在不同的文件种

3.不同的文件又被同一个模板引用

例:
```js
    <script src="jquery.js"></script>
    <script src="main.js"></script>
    <script src="dep.js"></script>
```

文件分离是最基础的模块化第一步

问题:

*污染全局作用域 =》不利于大型项目的开发以及多人团队的共建

二、模块化雏形—— IIFE (语法侧的优化)

本质: 作用域的把控

  例:
    //定义一个全局变量
    let count = 0
    const increase = () => ++count;
    const reset = () =>{
         count = 0
    }
    increase();
    reset();

利用函数块级作用域

    (() =>{
       let count =0;
    })();
    //在这里仅定义了一个函数,立即执行

接下来定义一个最简单的模块

     const iifeModule = (()=>{
        let count = 0;
        return {
            increase: () => ++count;
             reset: () =>{
                count = 0
             }
           }
    })();
    iifeModule.increase();
    iifeModule.reset()

问题1: 有额外依赖时,如何优化IIFE相关代码?

优化:依赖其他模块的IIFE

    //将其他模块当做参数进行传递
    const iifeModule = ((dependencyModule1,dependencyModule2)=>{
        let count = 0;
        return {
            increase: () => ++count;
             reset: () =>{
             count = 0
            }
          }
    })(dependencyModule1,dependencyModule2);
    iifeModule.increase();
    iifeModule.reset()

三、CJS - CommonJS(node.js 制定)

特征:

  • 通过module + exports 对外暴露接口
  • 通过require 来调用其他模块

模块组织方式 main.js

    //引入部分
    const dependencyModule1 = require(./dependencyModule1);
    const dependencyModule2 = require(./dependencyModule2);
    //处理部分
     let count = 0;
     const increase = () => ++count;
     const reset = () =>{
                 count = 0;
                }
    ...
    
    //暴露接口部分
    exports.increase = increase;
    exports.reset = reset;
    module.exports = {
        increase, reset
    }

模块使用方式

    const { increase, reset } = require('./main,js')
    
    increase();
    reset();

实际执行处理

    (function(thisValue, exports, require, module) {
        const dependencyModule1 = require(./dependencyModule1);
        const dependencyModule2 = require(./dependencyModule2);
        
         //业务逻辑
        ...
       
    }).call(thisValue, exports, require, module);

优点:

  • CommonJS率先在服务器端实现了从框架层面解决依赖、全局变量污染的问题

缺点:

  • 主要针对了服务端的解决方案。对于异步拉取依赖的处理整合不友好。

新的问题 —— 异步依赖

四、AMD规范

通过异步加载 + 允许制定回调函数

经典实现框架:require.js

新增定义方式:

    //通过define 来定义一个模块, 然后require 进行加载
    /*
    define
    params: 模块名, 依赖模块, 工厂方法
    */
    define(id, [depends], callback);
    require([module], callback);

模块定义方式

    //定义一个amdModule 的模块,依赖dependencyModule1 和dependencyModule2 模块
   define('amdModule',
          ['dependencyModule1','dependencyModule2'],
          (dependencyModule1,dependencyModule2))=>{
           //业务逻辑
           //处理部分
                let count = 0;
                const increase = () => ++count;
                const reset = () =>{
                     count = 0
                    }
                return {
                    increase, reset
                  }
            })

引入模块

        require(['amdModule'], amdModule =>{
            amdModule.increase();
        })

问题2: 如果在amdModule中想兼容已有代码,如何处理

    //define中提供了require方法
    define(require =>{
        //引入部分
        const dependencyModule1 = require('./dependencyModule1');
        const dependencyModule2 = require('./dependencyModule2');
        //处理部分
         let count = 0;
         const increase = () => ++count;
         const reset = () =>{
             count = 0;
            }
         return {
             increase, reset
         }
    })

问题3: AMD中使用revealing

    define('amdModule', [], (require, export, module) =>{
        //引入部分
        const dependencyModule1 = require('./dependencyModule1');
        const dependencyModule2 = require('./dependencyModule2');
        //处理部分
         let count = 0;
         const increase = () => ++count;
         const reset = () =>{
             count = 0;
            }
         export.increase = increase();
         export.reset = reset();
    })
    
    define('amdModule', [], require =>{
        const otherModule = require('amdModule');
        otherModule.increase();
        otherModule.reset();
    })

问题4: 兼容AMD & CJS

UMD的出现

     (define('amdModule', [], (require, export, module) =>{
            //引入部分
            const dependencyModule1 = require(./dependencyModule1);
            const dependencyModule2 = require(./dependencyModule2);
            //处理部分
             let count = 0;
             const increase = () => ++count;
             const reset = () =>{
                 count = 0;
                }
             export.increase = increase();
             export.reset = reset();
     }))(
         //区分CommonJS or AMD
         typeof module === "object"
         && module.exports
         && typeof define !== "function"
         //判断是否为CJS
         ?  //是JS
         factory =>module.exports = factory(require, export, module)
         :  //是AMD
         define
     );

优点:

  • 适合在浏览器中加载异步模块,可以并行加载多个模块

缺点:

  • 会有引入成本, 不能按需加载

五、CMD规范

主要应用的框架 sea.js

    define('module', (require, export, module)=>{
        let $ = require('jueqry');
        
        let dependencyModule1 = require('./dependencyModule1')
    })

优点:

  • 按需加载, 依赖就近 缺点:
  • 依赖于打包, 加载逻辑存在于每个模块中, 扩大模块体积

六、ES6模块化

新增定义:

引入关键字 —— import

导出关键字 —— export

模块引入、导出 和定义的地方

    //引入部分
    import dependencyModule1 from './dependencyModule1';
    import dependencyModule2 from './dependencyModule2';
    //代码逻辑部分
     let count = 0;
     //导出方法1
    exports const increase = () => ++count;
    exports const reset = () =>{
                 count = 0;
                }
    ...
    
    //导出方法2
    exports default{
        increase,reset
    }

模板引入

    <script type="module" src="esModule.js"></script>
    
    
    //node中
    import {increase, reset} from './esModule.mjs'
    increase();
    reset()
    //或
    import esModule from './esModule.mjs'
    esModule.increase();
    esModule.reset()

问题6: 动态模块

ES11原生解决方案:

    import('./esModule.mjs').then(dymaicEsModule =>{
        dymaicEsModule.increase
    })

七、模块化新思路 —— 前端工程化

根本问题 —— 运行时进行依赖分析(前端的模块化处理方案依赖于运行时分析)

解决方案 —— 线下执行(grunt gulp webpack)

    <!doctype html>
    <script src="main.js"></script>
    <script>
        // 给构建工具一个标识位
        require.config(_FRAM_CONFIG_);
    </script>
    <script>
        require(['a','e'],()=>{
            //业务处理
        });
    </script>
    </html>
    define('a',()=>{
        let b = require('b');
        let c = require('c');
        export.run = (){
            //run
        }
    })
工程化实现 (处理思路)

step1: 扫描依赖关系表:

    {
        a:['b','c'],
        b:['d'],
        e:[]
    }

step2: 重新生成依赖数据模板

     <!doctype html>
     <script src="main.js"></script>
     <script>
        // 构建工具生成数据
        require.config({
            "deps":{
                a:['b','c'],
                b:['d'],
                e:[]
            }
        });
     </script>
     <script>
        require(['a','e'],()=>{
            //业务处理
        });
     </script>
     </html>

step3: 执行工具, 采用模块化方案解决模块化处理依赖

    define('a',['b','c',],()=>{
        //执行代码
         export.run = (){
            //run
        }
    })

优点

  • 构建时生成配置,运行时执行
  • 最终转化成执行处理依赖
  • 可以拓展

最终形成 webpack为核心的工程化 + MVVM框架组件化 + 设计模式