Skip to content

模块化

CommonJS · AMD · CMD · ESM · Tree Shaking · IIFE


一、模块规范总览

规范加载方式适用场景代表
CommonJS同步/运行时加载服务端(Node.js)require/module.exports
AMD异步加载,依赖前置浏览器端require.js
CMD异步加载,依赖就近浏览器端sea.js
ESM编译时加载通用(现代标准)import/export

二、CommonJS

  • Node.js 是主要实践者,四个环境变量:moduleexportsrequireglobal
  • module.exports 定义输出接口,用 require 加载
  • 同步加载,服务端读磁盘快没问题,浏览器端应使用异步方案

module.exports vs exports

  • exports = module.exports = {},exports 是 module.exports 的一个引用
  • require 返回的是 module.exports,不是 exports
  • exports.xxx = ... → 修改了 module.exports 的属性,对外可见
  • exports = xxx → 重新赋值,切断了与 module.exports 的关系,对外不可见
  • 建议:统一使用 module.exports

三、AMD 与 CMD

AMD(require.js)

  • 异步加载模块,模块加载不影响后续语句执行,依赖模块在回调中执行
  • 特点:依赖前置、提前执行(声明时即加载执行所有依赖)

CMD(sea.js)

  • 与 AMD 类似,核心区别:依赖就近、延迟执行

四、ES6 Module

  • 语言标准层面的模块方案,浏览器和服务器通用
  • 两个命令:export(对外接口)和 import(引入功能)
  • import 会被 JS 引擎静态分析,编译时引入(非运行时),无法条件加载
  • 支持 tree-shaking(静态分析去除未使用代码)

导出方式对比

CommonJSES6 命名导出ES6 默认导出
导出module.exports = { test }export function test(){}export default test
导入const fn = require('./模块')import { test } from './模块'import test from './模块'
解构fn.test(1)test(1)test(1)

CommonJS vs ESM 核心差异

  • 输出值:CommonJS 输出值的拷贝(模块内部变化不影响已输出值),ESM 输出值的引用(动态引用,原始值变了 import 的值也变)
  • 加载时机:CommonJS 运行时加载整个模块对象,ESM 编译时静态分析按需加载
js
// CommonJS:值的拷贝
var myData = require("./module1").data;
updateData();
console.log(myData); // data(拷贝,不受影响)

// ESM:值的引用
import { data, updateData } from "./module1.js";
updateData();
console.log(data); // newData(引用,同步变化)

五、Tree Shaking

  • 概念:通过静态分析去除未使用的代码(DCE,Dead Code Elimination)
  • 仅对命名导出(export function xxx())生效export default 导出对象无法静态分析
  • 前提条件:必须使用 ES 模块规范(静态分析才能确定依赖关系)
  • Vue3 全面拥抱函数式编程、按需导入的原因之一

构建层 Tree Shaking 实战详见 工程化 · Webpack


六、"伪"模块化(IIFE + 闭包)

js
(function(window) {
  var data = "data";  // 私有变量
  function showData() { console.log(data); }
  function updateData() { data = "newData"; }
  window.module1 = { showData, updateData };  // 暴露接口
})(window);
  • 传入 window 的原因:① 全局变量变局部变量,访问更快;② 压缩时更优

演示:模块模式模拟

浏览器中无法直接运行 CommonJS,用对象模拟模块的导出/导入行为。

关键观察:

  • "模块文件" 通过 module.exports 暴露接口
  • "入口文件" 通过 require 获取整个模块对象
  • 命名导出只暴露指定的函数,默认导出暴露整个值

相关