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
相关推荐
恋猫de小郭1 小时前
Flutter Zero 是什么?它的出现有什么意义?为什么你需要了解下?
android·前端·flutter
崔庆才丨静觅7 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60618 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了8 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅8 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅9 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅9 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment9 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅9 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊9 小时前
jwt介绍
前端