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
相关推荐
vipbic6 小时前
中后台越做越乱后,我用插件化把它救回来了
前端·vue.js
Hyyy6 小时前
Computer Use 适合做什么,不适合做什么——一次真实使用后的思考
前端
小和尚同志6 小时前
前端 AI 单元测试思考与落地
前端·人工智能·aigc
invicinble7 小时前
c端系统,其实更像一个信息展示平台
前端
你怎么知道我是队长8 小时前
JavaScript的变量和数据类型介绍
开发语言·javascript·ecmascript
李姆斯8 小时前
管理是否可以被完全量化
前端·产品经理·团队管理
名字还没想好☜9 小时前
Next.js 中间件实战:鉴权、重定向与 A/B 分流
开发语言·前端·javascript·中间件·react·next.js
广州灵眸科技有限公司9 小时前
瑞芯微RV1126B开发板(EASY-EAI-PI2) INI文件操作
java·前端·javascript·网络·人工智能
心中有国也有家9 小时前
AtomGit Flutter 鸿蒙客户端:ModalBottomSheet 实战
android·javascript·学习·flutter·华为·harmonyos
江华森10 小时前
Web 开发基础:HTTP 与 REST
前端·网络协议·http