JS模块化

103 阅读4分钟

JS模块化

1.历史

JS本身就是为了满足简单的页面设计:页面动画 + 表单提交并无模块化或者命名空间概念

JS的模块化需求日益增长

幼年期:无模块化

  1. 开始需要在页面中加载不同JS:动画、组建、格式化
  2. 多种js文件会被分在不同的文件中
  3. 不同的文件又被同一个模板所引用
    <script src='main.js'></script>
    <script src='demo.js'></script>

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

  • 拓展
  • script标签的参数 —— async & defer
    <script src='main.js' async></script>

无参数 —— 解析到立即阻塞,立刻下载执行当前script async —— 解析到标签开始异步下载,解析完成后开始执行 defer —— 解析到标签开始异步下载,下载完成后开始执行并且阻塞渲染,执行完成后继续渲染

截屏2023-05-12 11.33.52.png

兼容性 >IE9

存在问题:

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

成长期: 模块化雏形 —— IIFE(语法侧的优化)

作用域的把控
    let count=0;
    const increase = ()=> ++count;
    const reset = ()=>{
        count=0;
    }

利用函数的块级作用域 —— 隔离区

(()=>{
    let count=0;
    const increase = ()=> ++count;
    const reset = ()=>{
        count=0;
    }
})();

这样就初步实现一个最最最简单的模块

  • 扩展
  • 独立模块本身的依赖优化

方法1: 依赖其他模块传参,把需要加载的模块通过参数传进去

    const iifeModule = ((dependencyModule1,dependencyModule2)=>{
       let count=0;
    const increase = ()=> ++count;
    const reset = ()=>{
        count=0;
    } 
    ……
    return{
        increase,reset
    }
    })(dependencyModule1,dependencyModule2);

成熟期:

CJS — Commonjs

node.js 指定

特征:

  • 通过 module + exports 去对外暴露接口
  • 通过require 去引入外部模块
    const dependencyModule1=require('./dependencyModule1');
    const dependencyModule2=require('./dependencyModule2');
    let count=0;
    const increase = ()=> ++count;
    const reset = ()=>{
        count=0;
    } 
    increase();
    
    exports.increase=increase;
    exports.reset=reset;
    
    module.exports={
        increase,reset
    }
    //使用
    const { increase,reset } =require('./main.js')
    
    //复合使用
    
    (function(){
        const dependencyModule1=require('./dependencyModule1');
    const dependencyModule2=require('./dependencyModule2');
    ……
    }).call(thisValue,exports,require,module);
    
    //一些开源项目为啥要把全局、指针以及框架本身引用作为参数传给本身
    (function(window,$,undefined){
        const _show = function(){
            $('#app').val("hi.  ")
        }
    })(window,jQuery)
    window
    1. 全局作用域转化成局部作用域,提升执行效率
    2. 编译时优化
    (function(c){})(window)  //window会被优化成c,执行结束之后c会被销毁
    jquery 
    1. 独立定制复写和挂载
    2.防止全局串扰
    undefined
    防止重写
  • 优点: CommonJs率先在服务端实现了从框架层面解决了依赖,全局变量污染的问题
  • 缺点: 针对了服务端的解决方案,异步来取依赖处理不是很完美

新的问题 —— 异步依赖

AMD规范

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

经典实现框架:require.js

新增定义方式:

    //define 来定义模块
    define(id,[depends],callback);
    //require 进行加载
    require([modules],callback);

模块定义地方

    define('amdModule',['dependencyModule1','dependencyModule2'],(dependencyModule1,dependencyModule2)=>{
        ……
    })

引入的地方

    require(['amdModule'],amdModule=>{
        amdModule.xxx();
    })
  • 扩展:在AMDModule中兼容已有代码
    define('amdModule',[],require=>{
      const dependencyModule1=require('./dependencyModule1');
      const dependencyModule2=require('./dependencyModule2');
        ……
    })
  • 手写兼容CJS & AMD
    //判断关键1. object 还是 function 2. 有没有export 3. define
    (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();
    })(
        //一次性区分CJS和AMD
        typeof module === 'object' && module.exports && typeof define !== 'function'? //CJS
        factory => module.exports = factory(require,exports,module)
        : //AMD
        define
    )
    
  • 优点: 适合在浏览器中加载异步模版的方案
  • 缺点: 引入成本,define形式很难做到绝对的按需加载

CMD

按需加载 主要应用框架: sea.js

    define('module' , (require,exports , module)=>{
        let $=require('jquery');
        //jquery相关
        ……
        let dependencyModule1=require('./ dependencyModule1');
        //dependencyModule1 相关逻辑
        ……
        
    })
  • 优点: 按需加载,依赖就近
  • 缺点: 依赖打包,加载逻辑存在于每个模块中,扩大了模块体积,同时功能上依赖编译

ES6模块化

走向新时代

新增定义

引入:import

导出:export

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

    //引入
    import dependencyModule1 from './dependencyModule1';
    import dependencyModule2 from './dependencyModule2';
    ……
    //导出
    export const increase=()=> ++count;
    export const reset = ()=>{
        count=0;
    }
    
    export default{
        increase,reset
    }

ES动态模块原生解决方案

    import('./esModule.js').then(dynamicModule=>{
        dynamicModule.increase();
    })
  • 优点(重要性): 通过一种最终统一各端的形态,整合了js模块化的通用方案
  • 局限性:本质上还是运行时的依赖分析

截屏2023-05-12 18.11.50.png

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

遗留问题

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

解决方案 - 线下执行

简述实现一个编译依赖的思路

    <! doctype html>
        <script src='main.js'></script>
        <script>
        //给构建工具一个标识位
            require.config(__FRAME_CONFIG__);
        </script>
        <script>
            require(['a','e'],()=>{
                ……
            })
        </script>
    </html>
    define('a',()=>{
        let b = require('b')
        let c = require('c')
    })
工程化扫描
  1. 扫描依赖关系表
    {
        a:['b','c'],
        b:['d'],
        e:[]
    }
  1. 根据依赖关系表重制模版
    <! doctype html>
        <script src='main.js'></script>
        <script>
        //给构建工具生成数据
            require.config({
                "deps":{
                   a:['b','c'],
                   b:['d'],
                   e:[] 
                }
            });
        </script>
        <script>
            require(['a','e'],()=>{
                ……
            })
        </script>
    </html>
    define('a',()=>{
        let b = require('b')
        let c = require('c')
    })
  1. 执行工具,采用模块化解决方案处理
    define('a',['b','c'],()=>{
        export.run = ()=>{
            ……
        }
    })

优点:

  1. 构建时生成配置,进行时去运行
  2. 最终转化为可执行的依赖处理
  3. 可以拓展

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