JS 模块化

146 阅读5分钟

JS 历史

背景

JS本身简单的页面设计:页面动画 + 页面提交,
并没有模块化 or 命名空间的概念

幼年期

1.开始需要在页面添加一些不同的JS : 动画,表单,格式化
2.多种JS文件被分在不同的文件中
3.不同的文件又被同一个模板引用

```js
    <script scr='jquery.js'></script>
    <script scr='main.js'></script>
    <script scr='dep1.js'></script>
```

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

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

成长期

   模块化的雏形 -- IIFE(语法侧的优化)

作用域的把控

    let count = 0
    
    // 例子一
    const increase = () => ++count
     // 例子二
    const reset = () =>{  count = 0 }

    increase()

    reset()

利用函数块级作用域

   ```js
    (()=>{
    let count = 0
     // ...
    }();
   
   ```
 初步实现了一个最最最简单的一个模块   
 

尝试去定义一个最简单的模块

    const ifModule = (()=>{
        let count = 0 
        return  {
         const increase = () => ++count
           // 例子二
         const reset = () =>{  count = 0 }
        }
    })
    
    ifModule.increaas();
    ifModule.reset();  

追问:有额外依赖时,如何优化IIFE相关代码 >优化1:依赖其他模块的IIFE

   const ifModule = ((value1,value2)=>{
       let count = 0 
       return  {
        const increase = () => ++count
          // 例子二
        const reset = () =>{  count = 0 }
       }
   })(value1,value2)
   
   ifModule.increaas();
   ifModule.reset();  

面试1:早期juquery的依赖处理以及模块加载方式吗

了解传统IIFE是如何解决多方依赖的问题

答:IIFE加参条调配

实际上,jquery,等框架其实应用了revealing(揭示模式) 写法

     const ifModule = ((value1,value2)=>{
         let count = 0 
         
          const increase = () => ++count
            
          const reset = () =>{  count = 0 }
         return  {
             increase,
             reset
         }
     })(value1,value2)
     
     ifModule.increaas();
     ifModule.reset(); 

成熟期

CJS - commonjs

node.js 指定

特性

通过 module + export 去对外暴露接口

通过require 来调用其他模块

main.js 部分

    // 引入部分
    const dependencyModule1 = require('./dependencyModule1');
    const dependencyModule2 = require('./dependencyModule2');
    
    // 处理部分
     let count = 0 
     const increase = () => ++count     
     const reset = () =>{  count = 0 }
     // 做一些跟引入依赖相关事宜...
    
    // 暴露接口部分
    export.increase = increase;
    export.reset = reset;
    
    module.expors ={
        increase,
        reset
    }
    

模块使用方式

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

可能被问到的问题 实际执行处理

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

commjs 优点

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

commjs 缺点

主要针对于服务端的解决方案。对于异步拉去依赖的处理整合不是那么友好

新的问题 —— 异步依赖

AMD 规范

通过异步加载 + 允许指定回调函数 经典时间线框架是 require.js

新增定义方式:

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

模块定义方式

    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(amdModule=>{
        // 引入部分
        const dependencyModule1 = require('./dependencyModule1');
        const dependencyModule2 = require('./dependencyModule2');

        // 处理部分
         let count = 0 
         const increase = () => ++count     
         const reset = () =>{  count = 0 }
         // 做一些跟引入依赖相关事宜...

        // 暴露接口部分
        export.increase = increase;
        export.reset = reset;

        return {
            increase,
            reset
        } 
    })
   

**面试题3: AMDmodule 中使用 revealing **

    define('amdModule',[],(require,export, module)=> {
        amdModule=>{
        // 引入部分
        const dependencyModule1 = require('./dependencyModule1');
        const dependencyModule2 = require('./dependencyModule2');

        // 处理部分
         let count = 0 
         const increase = () => ++count     
         const reset = () =>{  count = 0 }
         // 做一些跟引入依赖相关事宜...

        // 暴露接口部分
        export.increase = increase;
        export.reset = reset;

         export.increase = increase();
         export.reset = reset();
        }
    })
    
    define('amdModule',[],requirre=>{
        const otherModule = require('amdModule')
        otherModule.increase();
        otherModule.reset();
    })
   

面试题3: 兼容 AMD & commonjs / 如何判断 CJS

UMD 出现

    ( define('amdModule',[],(require,export, module)=> {
        amdModule=>{
        // 引入部分
        const dependencyModule1 = require('./dependencyModule1');
        const dependencyModule2 = require('./dependencyModule2');

        // 处理部分
         let count = 0 
         const increase = () => ++count     
         const reset = () =>{  count = 0 }
         // 做一些跟引入依赖相关事宜...

        // 暴露接口部分
        export.increase = increase;
        export.reset = reset;

         export.increase = increase();
         export.reset = reset();
        }
    }))(
        // 目标是一次性区分commonjs or AMD
        typeof module === 'object'
        && module.exports 
        && typeof define !== 'function'
        ?// 是 CJS 
        // factory 执行的是一个回调
        factory => module.exports = factory(require,exports,module)
        : // 是AMD 
        define
    )

优点: 适合在浏览器中加载异步模块,可以并行加载多个模块 缺点: 会有引入成本,不能按需加载

CMD 规范

按需加载

主要用的框架 sea.js


    define('module',[],(require,exports,module)=>{
        let $ = require('jquery')
         // jquery 相关逻辑
         
        let dependencyModule = require('./dependencyModule')
        // dependencyModule1 相关逻辑
    })

优点: 按需加载,依赖就近

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

面试题5: AMD & CMD 区别

答: 依赖就近,按需加载

ES6 模块化

走进新时代

新增定义:

引入关键字 —— import

导出关键字 —— export

    // 引入区域
    const dependencyModule1 = require('./dependencyModule1');
    const dependencyModule2 = require('./dependencyModule2')
    
    // 实现代码逻辑
   
   let count = 0 
   const increase = () => ++count     
   const reset = () =>{  count = 0 }
    
    // 导出区域
    export default {
        increase,reset
    }

模板引入的地方

    <script type='module'  scr="esModule.js"></script>

node引入的地方

    import { increase , reset } = './esMoule.js' 
    increase();
    resset();
    
    import esModule from './esModue.mjs'
    esModule.increase();
    esModule.resset();

面试题6: 动态模块

答: export import

ES11原生解决方案:

    import('./exModule.js').then(dynamicEsModule => {
        dynamicEsModule.increase();
    })

优点(重要点): 通过一种最统一的形态政和路js的模块化

缺点(局限性):本质上还是运行时的依赖分析

解决模块化的新思路 —— 前端工程化

背景

根本问题 - 运行时 进行依赖分析

前端的模块化处理方案依赖于运行时分析

解决方案: 线下执行 grunt gulp webpack

工程化的实现

<!DOCTYPE html>
<html>
    <script scr="main.js"></script>
    <script>
        // 给构建工具一个标识位
        webpack.config(_FRAME_CONFIG_);
    </script>
    <script>
           require(['a'],()=>{
               // 业务处理
           })
    </script>
</html>
    define('a',()=>{
        let b = require('b')
        export.run = () {
            // run
        }
    })

工程化实现

step1: 扫描依赖关系表:

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

step2: 重新申城依赖数据模板:

  <!DOCTYPE html>
<html>
    <script scr="main.js"></script>
    <script>
        // 给构建工具一个标识位
        webpack.config({
            'deps':  {
                a:['b','c'],
                b:['d'],
                e:[]
            }
        });
    </script>
    <script>
           require(['a','e'],()=>{
               // 业务处理
           })
    </script>
</html>

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

     define('a',['b','c'],()=>{
        export.run = () => {}
    })

优点: 1.构建时生产配置,运行时运行 2.最终转化成执行处理依赖 3.可以扩展

完全体 webpack 为核心的工程化 + mvvm 框架组件化 + 设计模式