04ES 模块化

文件一多,全局变量就会互相踩脚。ES Module(import / export) 是浏览器原生的「分文件、按依赖组装」方案: 每个文件自带作用域,只暴露你写 export 的东西。 这也是后面学 Vite、React、Vue 的共同底座。

约 15 分钟 4 个动手实验 工程化起点

一、为什么需要模块

以前全局脚本

// a.js
var name = 'A';

// b.js
var name = 'B';  // 覆盖了 A!

// 引入顺序错了就报错
<script src="a.js"></script>
<script src="b.js"></script>

现在ES Module

// a.js
export const name = 'A';

// b.js
import { name } from './a.js';
console.log(name);  // 'A',不会被覆盖

// main.js
<script type="module" src="main.js"></script>
Module 三件套:文件作用域隔离、 显式 import/export、 自动严格模式。还能被浏览器预加载与 tree-shake。

二、导出 export:具名 vs 默认

导出导入说明
export const a = 1 import { a } from './m.js' 具名导出,可导多个
export default fn import fn from './m.js' 默认导出,每文件最多一个
export { a, b } import { a } from './m.js' 打包导出,可 as 重命名
export * from './x.js' 透传再导出 做「入口文件」常用
在单文件里模拟两个模块的「导出 / 导入」关系:① 具名 ② 默认 ③ 动态 import

三、模块的执行特性(面试常问)

特性普通 scripttype="module"
作用域共享全局文件私有
严格模式默认否永远是
加载阻塞 / defer / async默认类似 defer,按序执行
顶层 await不允许允许(现代浏览器)
重复加载执行多次只执行一次(有缓存)
import 位置—必须在模块顶层(不能在 if 里静态 import)
静态 import 会被提升到模块顶部; 条件加载请用动态 import()(返回 Promise)。

四、file:// 下模块会挂?

用双击打开 HTML 时,浏览器常因 CORS 拒绝加载本地 .js 模块。这不是你代码写错,是安全策略。

本站课程实验台为了离线可用,用「单文件 + 函数模拟模块」演示概念; 真实项目请开本地服务器写多文件 type="module"。

五、模块组织建议(小项目就够用)

六、常见问题 · 自问自答

Q

为什么 import 写在函数 / if 里会报错?

A

静态 import 是编译期依赖,必须在模块顶层。 要按条件加载,用 const m = await import('./x.js'), 或把逻辑写在 if 里调用动态 import。

Q

export default 和具名导出该怎么选?

A

现代实践优先具名导出:改名可搜索、跳转方便、利于 tree-shaking。 默认导出适合「本文件就一个主角」的场景(如 React 组件页)。 同一文件也可以两者都有,但团队规范最好统一。

Q

为什么导入的变量改了值,源模块里也变了?

A

ESM 导入的是活绑定(live binding),不是值拷贝。 源文件里重新赋值,导入方能看见。要「复制一份」请自己 structuredClone 或写纯函数返回新对象。

Q

报错 Failed to load module / CORS 怎么办?

A

① 是否用 file:// 双击打开 → 改本地服务器; ② 导入路径是否写了 ./ 或 ../(相对路径不能省); ③ 后缀是否是 .js(浏览器原生要求完整扩展名); ④ 是否把 type="module" 写对。

Q

两个模块互相 import 会死循环吗?

A

ESM 能处理环依赖,但初始化顺序仍可能导致「先用后赋值」得到 undefined。尽量单向依赖; 必须共用类型/常量时,抽第三个「共享模块」。

七、本章小结

  • type="module" = 文件作用域 + 严格模式 + 按序延迟执行。
  • 导出:具名 / 默认;导入名字要对得上,可 as 重命名。
  • 静态 import 必须在顶层;按需加载用动态 import()。
  • 本地开发请起 HTTP 服务器,别用 file:// 硬扛 CORS。
  • 组织原则:一文件一职责,具名导出优先,依赖单向。
动手练习: ① 拆出 utils/format.js(格式化日期)在页面里 import 使用; ② 用动态 import 实现「点按钮才加载」一段动画逻辑; ③ 故意在 if 里写 import,把报错记进笔记。