JS模块化发展

JS 模块化

背景

JS 本身定位:简单的页面设计 - 页面逻辑简单处理 + 表单提交以及基本动画 并无模块化、命名空间的概念

JS 的模块化需求日益增长

幼年期:无模块化协调阶段

  1. 开始需要在页面中增加一些不同的 JS:动画、表单、格式化工具
  2. 多种 JS 文件被分在不同的文件夹中
  3. 不同的文件被同一个模块所引用
js 复制代码
//  index.html
<script src="js/animation.js"></script>
<script src="js/form.js"></script>
<script src="js/format.js"></script>
<script src="js/main.js"></script>

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

script标签:- async | defer

js 复制代码
// 1. script 加载 + 解析
// 默认阻塞模式
// 阻塞页面渲染,加载完再渲染。适合优先级较高
<script src="main.js"></script>
// async
//  异步加载,加载完立即执行。适合无优先级的大文件
<script src="main.js" async></script>
// defer
// 延迟模式,加载完等待页面渲染完成再执行。 延迟解析,任何JS都不会阻塞页面渲染,适合优先级较低模块
<script src="main.js" defer></script>
// 2.加载场景:页面加载时,需要加载的JS文件

问题

  • 变量名冲突,污染全局作用域;不利于大型项目开发以及多人协作

成长期:模块化的雏形 - IIFE + 立即执行函数

作用域的把控

js 复制代码
    // 定义一个全局变量
    let count = 0;
    // main.js - 代码块1
    const main = () => {
        // ......
        const count = 1;
        return {
            default: {
                // ......
            },
        };
    };
    // tools.js - 代码块2
    const tools = () => {
        // ......
        const count = 2;
        return {
            // ......
        };
    };
    let mainResult = main();
    // mainResult.default
    let toolsResult = tools();
    // 返回值不冲突就可以使用
  • 利用 - 立即执行函数,将代码块包裹

  • IIFE 立即执行函数,将代码块包裹起来,形成独立的作用域,防止变量污染

    定义函数 + 立即执行 => 形成一个独立的空间 初步实现一个最简单的模块

js 复制代码
    const module = (() => {
        // main.js
        let count = 0;
        return {
            increase: () => ++count,
            reset: () => {
                count = 0;
            },
        };
    })();

    module.increase();
    module.reset();

有额外依赖的时候,如何优化 IIFE

优化:依赖其他模块的 IIFE

js 复制代码
    const module = ((dependModule1,dependModule2) => {
        // main.js
        let count = 0;
        return {
            increase: () => ++count,
            reset: () => {
                count = 0;
            },
        };
        // ...dependModule1.xxx,dependModule2.xxx
    })(dependModule1,dependModule2);

追问

  1. 深入模块化的多种方案
  2. 转向框架 - jQuery Vue react
  3. 转向设计模式 - 注重模块化的设计模式

成熟期

CJS - commonJS

Node.js 制定 特征

  • 通过 module + export 去对外暴露接口
  • 通过require来调用其他模块 模块组织方式
js 复制代码
<!---->

    // tool.js
    // 引入部分
    const dependencyModule0 = require("./dependencyModule0");
    const dependencyModule1 = require("./dependencyModule1");

    // 核心逻辑
    let count = 0;
    const increase = () => ++count;
    const reset = () => {
        count = 0;
    };
    module.exports = {
        increase,
        reset,
    };

    // 暴露选择接口
    exports.increase = increase;
    exports.reset = reset;

<!---->

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

    increase();
  • 优点CJS率先在服务端实现了从架构层面解决依赖、全局变量污染的问题
  • 缺点CJS针对服务端,对于异步场景没有很友好地处理和考虑

AMD 规范

通过异步加载 + 允许定制回调函数 - require.js

特征:

js 复制代码
    // 通过 define 来定义模块
    define(id, [depends], callback);
    require([module], callback);

<!---->

    define("amdModule", ["dependencyModule1", "dependency2"], (
        dependencyModule1,
        dependency2
    ) => {
        // 业务逻辑
        let count = 0;
        const increase = () => ++count;
        // ...dependencyModule1
    });

<!---->

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

面试题:兼容判断 AMD & CJS

js 复制代码
`define` => AMD `exports` =>CJS

    define(
        "amdModule",
        ["dependencyModule1", "dependency2"],
        (dependencyModule1, dependency2) => {
            // 业务逻辑
            let count = 0;
            const increase = () => ++count;
            // ...dependencyModule1
        }
    )(
        typeof module === "object" && module.exports && typeof define !== "function"
            ? // 是CJS
              (factory) => (module.exports = factory(require, exports, module))
            : // 是AMD
              define
    );

    // 面试方向:UMD

<!---->

    // UMD 实现
    (function (root, factory) {
      if (typeof define === "function" && define.amd) {
        // AMD 如果环境中有define函数,并且define函数具备amd属性,则可以判断当前环境满足AMD规范
        define(["jquery"], factory);
      } else if (typeof exports === "object" && typeof module === "object") {
        // CommonJS规范 node 环境 判断是否支持 module.exports 支持 require 这种方法
        module.exports = factory(require("jquery"));
      } else {
             // 直接挂载在全局对象上
        root.returnExports = factory(root.jQuery);
      }
    })(this, function ($) {
      // ...
    });
  • 优点:解决了浏览器中异步加载模块的问题
  • 缺点:有了引入成本,没有考虑按需加载

CMD 工具框架 - sea.js

按需加载

js 复制代码
    define("module", (require, exports, module) => {
        let $ = require("jquery");
        // ...
        let dependencyModule1 = require("./dependencyModule1");
        // ...
    });
  • 优点:按需加载,依赖就近,性能和谐
  • 缺憾:依赖于打包,加载逻辑会实际打包到模块中,增加了模块体积

ESM - ES6

新增定义:引入关键字 import 导出关键字 export

js 复制代码
    import dependencyModule1 from "./dependencyModule1";
    import dependencyModule2 from "./dependencyModule2";

    // 业务逻辑
    let count = 0;
    const increase = () => ++count;
    // ...dependencyModule1
    export default {
        increase,
    };
    // 模块核心 公用

ES11

js 复制代码
    import("./dependencyModule1.js").then((dynamicESModule) => {
        dynamicESModule.increase();
    });

优点 :通过一种最统一的形态整合了 JS 的模块化 缺憾:本质上还是运行时的依赖分析

面试方向:

  1. 工程化
  2. ECMAScript
相关推荐
傻小胖4 分钟前
nodejs使用require导入npm包,开发依赖和生产依赖 ,全局安装
前端·npm·node.js
傻小胖7 分钟前
yarn的介绍与操作,yarn和npm的选择
前端·npm·node.js
掘金一周9 分钟前
掘金的广告越来越烦人了,悄悄把它隐藏起来🤫 | 掘金一周 4.23
前端·人工智能·后端
Go_going_27 分钟前
【解决 el-table 树形数据更新后视图不刷新的问题】
前端·javascript·vue.js
进取星辰37 分钟前
10、Context:跨维度传音术——React 19 状态共享
前端·react.js·前端框架
wfsm39 分钟前
react使用01
前端·javascript·react.js
小小小小宇1 小时前
Vue 3 的批量更新机制
前端
阳光普照世界和平1 小时前
从单点突破到链式攻击:XSS 的渗透全路径解析
前端·web安全·xss
MrsBaek1 小时前
前端笔记-AJAX
前端·笔记·ajax