欢迎光临
我们一直在努力

ES Module 入门到避坑(附 CJS 互操作要点,2026 版)

1. 为什么现在必须学会 ESM

如果只停留在“会写 import 语法”,很容易在工程化项目里踩坑。

ESM 真正重要的点在于:

  • 它是官方标准模块系统
  • 静态结构便于分析和优化
  • 是 tree-shaking、代码分割、top-level await 的基础
  • 一句话:ESM 不只是新语法,是现代前端工具链的默认前提。

    2. ESM 的核心语法

    2.1 导出:具名 + 默认

    // named export
    export const a = 1;
    export function b() {}

    // default export
    export default function main() {}

    要点:

    • 具名导出可以有多个
    • 默认导出最多一个
    • 两者可以同时存在

    2.2 导入:四类最常见写法

    import './setup.js';
    import { a, b } from './mod.js';
    import main from './mod.js';
    import * as ns from './mod.js';

    再补一个动态导入:

    const mod = await import('./mod.js');

    import() 返回 Promise,适合按需加载。

    3. 最容易误解的点:ESM 不是“导出一个普通对象”

    很多教程会把模块导出结果画成一个对象,这在教学演示上方便,但容易误导。

    更准确的理解:

    • 模块对外暴露的是一组“导出绑定”
    • import * as ns 拿到的是模块命名空间对象(Module Namespace Object)
    • 这不是你自己创建的普通对象字面量

    示意:

    import * as ns from './mod.js';
    console.log(ns.default);
    console.log(ns.someNamed);

    可以“像对象一样访问”,但语义不是“对象解构 + 值拷贝”。

    4. live binding:为什么导入变量不能重新赋值

    // counter.js
    export let count = 0;
    export function inc() {
    count++;
    }

    // main.js
    import { count, inc } from './counter.js';

    console.log(count); // 0
    inc();
    console.log(count); // 1

    // count = 2; // TypeError

    这说明两件事:

  • 导出方更新值,导入方会看到新值(live binding)
  • 导入方不能给导入绑定重新赋值
  • 这也是 ESM 和“对象解构”在语义上的关键差别。

    5. 顶层限制:不是“必须第一行”,而是“必须顶层”

    常见说法是“import/export 必须写在文件最上面”,这不严谨。

    更准确说法:

    • 静态 import / export 必须出现在模块顶层
    • 不能放进 if、循环、函数体
    • 实践中放顶部是风格约定,不是语法要求“第一行”

    6. 浏览器里的 ESM:你要记住的 4 个点

  • 入口脚本要写 type="module"
  • 模块默认延迟执行,通常不再需要 defer
  • 本地 file:// 直接打开容易有 CORS/加载问题,建议本地服务器
  • 相对路径通常写完整扩展名,如 ./utils.js
  • <script type="module" src="./main.js"></script>

    7. Node.js 里的 ESM:和浏览器相同但不完全相同

    Node.js 里常见启用方式:

  • 使用 .mjs
  • package.json 配 "type": "module"
  • 在 Node ESM 环境中:

    • 没有 require / module.exports
    • 没有 __filename / __dirname
    • 常用 import.meta.url(新版还有 import.meta.dirname 等)
    • 相对导入通常要显式扩展名

    示例:

    import { readFileSync } from 'node:fs';
    const text = readFileSync(new URL('./data.txt', import.meta.url), 'utf8');

    8. ESM 和 CommonJS 的互操作:

    Node.js 中 ESM 与 CJS 可以互操作,但建议记“最稳策略”:

    ESM 导入 CJS,优先默认导入:

    import pkg from './legacy.cjs';

    因为默认导入通常对应 module.exports。

    具名导入 CJS 有时可用,但依赖静态分析推断,跨环境稳定性不如默认导入。

    9. CJS vs ESM 速查表

    维度CJSESM
    导入 require() import / import()
    导出 module.exports / exports export / export default
    依赖分析 偏运行时 天然支持静态分析
    绑定语义 常围绕导出对象工作 live binding
    浏览器原生支持 不支持 支持
    工程化适配 需要更多工具兼容 更适合现代前端工具链

    10. 结语

    ESM 真正难的不是语法,而是语义和运行时差异。

    如果你把这 5 个点吃透,工程化里大部分模块化问题都能快速定位:

  • 导出的是绑定,不是普通对象
  • 导入是 live binding
  • 静态 import/export 必须顶层
  • 浏览器和 Node 的解析细节不同
  • CJS 互操作优先默认导入

  • 参考文档:

    • https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules
    • https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import
    • https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export
    • https://nodejs.org/api/esm.html

    版权声明:本文为学习整理稿,欢迎交流,转载请注明出处。

    赞(0)
    未经允许不得转载:171主机测评 » ES Module 入门到避坑(附 CJS 互操作要点,2026 版)
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址