Appearance
模块化
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 是主要实践者,四个环境变量:
module、exports、require、global - 用
module.exports定义输出接口,用require加载 - 同步加载,服务端读磁盘快没问题,浏览器端应使用异步方案
module.exports vs exports
exports = module.exports = {},exports 是 module.exports 的一个引用require返回的是module.exports,不是 exportsexports.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(静态分析去除未使用代码)
导出方式对比
| CommonJS | ES6 命名导出 | 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获取整个模块对象- 命名导出只暴露指定的函数,默认导出暴露整个值
相关
- 工程化 · Webpack — 打包构建与 Tree Shaking
- JS 引擎与执行上下文 — V8 引擎执行流程