大家好,我是程序员柏风,AI 最全的 Vibecoding 学习地图 http://coderoadmap.cn 作者。
ES Module 与 npm 概览
这一课你将能做到
- 说出为什么要把代码拆成多个文件,以及模块边界大概怎么切
- 看懂并手写最小的
import/export(命名导出) - 知道
package.json与npm install/npm run的基本含义
为什么要学
一个 HTML 里塞 2000 行 JS 会失踪。
模块让文件各管一块:工具函数一个文件,页面逻辑一个文件。
npm 是前端世界的「零件市场」:别人写好的库装进项目。
本站 platform 全是模块化 TS/JS;不理解 import,打开仓库会懵。
这一课目标是「混脸熟并能跑通最小例子」,不是背完整生态。
先建立图像
math.js 导出 add
main.js 导入 add 并使用
index.html 用 <script type="module"> 启动 main.js
package.json:项目说明书(依赖哪些包、有哪些脚本)
node_modules:安装下来的零件(通常不手改、不提交仓库)
package-lock.json:锁版本,保证大家装到同一套
| 词 | 人话 |
|---|---|
| 命名导出 | export function add → import { add } |
| 默认导出 | export default → import add(无花括号) |
| 依赖 | 项目运行需要的第三方包 |
| 脚本 | npm run xxx 触发的命令别名 |
跟着做
步骤 1:准备三个文件(同目录)
math.js:
// 命名导出:把 add 公开给其他文件
export function add(a, b) {
return a + b;
}
export function clamp(n, min, max) {
return Math.min(max, Math.max(min, n));
}
main.js:
// 从相对路径导入;浏览器原生 ESM 常要写 .js 后缀
import { add, clamp } from "./math.js";
console.log("2+3=", add(2, 3));
console.log("clamp 120 →", clamp(120, 0, 100));
const out = document.getElementById("out");
if (out) {
out.textContent = `add(2,3)=${add(2, 3)};clamp(120)=${clamp(120, 0, 100)}`;
}
index.html:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>ES Module</title>
</head>
<body>
<p id="out">加载中…</p>
<!-- 必须 type="module" 才能用 import -->
<script type="module" src="./main.js"></script>
</body>
</html>
步骤 2:用本地静态服务打开(重要)
直接 file:// 打开有时会因浏览器安全限制失败(CORS / 模块限制)。
任选一种:
# 若已装 Node
npx --yes serve .
# 或 VS Code 的 Live Server 插件点 Go Live
浏览器打开提示的本地地址,打开控制台:应看到计算结果,页面文字也被更新。
若失败:看控制台是否提示找不到 ./math.js、是否忘了 type="module"。
步骤 3:默认导出对照(别混)
greet.js:
export default function greet(name) {
return `你好,${name}`;
}
main.js 追加:
import greet from "./greet.js"; // 默认导出:无花括号
console.log(greet("同学"));
命名导出用花括号;默认导出不用。混用是初学高频报错来源。
步骤 4:读懂 package.json 字段
打开本站 platform/package.json(或任意前端项目)对照:
| 字段 | 人话 |
|---|---|
name / version | 包名与版本 |
dependencies | 运行需要的包 |
devDependencies | 开发/构建才要的包 |
scripts | 快捷命令,如 dev、build |
在项目根目录(有 package.json 的地方):
npm install # 按 package.json / lockfile 安装依赖
npm run dev # 跑 scripts 里的 dev(具体命令看文件)
你不需要一次记全生态,先会「安装依赖」和「跑脚本」两下。
步骤 5:再拆一个「纯函数模块」练习
新建 format.js:
export function formatScore(score) {
if (!Number.isFinite(score)) return "无效";
return `${score} 分`;
}
在 main.js 导入并调用,确认页面/控制台输出变化。
体会:模块边界按「职责」切——格式化、计算、DOM 操作可以分开。
步骤 6:常见报错对照演练
故意改错再修,加深记忆:
- 去掉
type="module"→ 应出现关于import的语法错误 - 写成
import { add } from "./math"(漏后缀)→ 浏览器原生 ESM 可能 404 - 把命名导出当成默认:
import add from "./math.js"→ 运行期/绑定错误
每修一处,回到「能跑」状态再继续。
讲清楚要点
- 因为单文件会膨胀,所以用模块按职责拆分并显式导出/导入。
- 因为浏览器要声明模块脚本,所以
<script type="module">不能省。 - 因为命名导出与默认导出语法不同,所以花括号别乱加乱删。
- 因为原生 ESM 常要求完整路径,所以浏览器里相对导入多写
./xx.js。 - 因为第三方库版本要可复现,所以用
package.json+ lockfile,而不是随手下载散脚本。 - 因为
node_modules巨大且可生成,所以加入.gitignore,用npm install恢复。
你可能会懵的地方
-
为什么要写
.js后缀?
浏览器原生 ESM 常要求明确后缀;打包工具(Vite/webpack)里规则可能不同。以当前环境的报错为准。 -
Node 和浏览器模块一样吗?
现代都在向 ESM 靠,细节有差别(扩展名、package.json的"type"等)。先建立 import/export 心智。 -
require是什么?
CommonJS 老风格,Node 里仍常见。新教程与前端构建多讲import。 -
为什么有人说「模块自动 defer」?
type="module"的脚本默认推迟执行、且保持顺序,行为与普通脚本略有不同——这也是推荐模块化的原因之一。
错法对照
| 错法 | 问题 | 正解 |
|---|---|---|
无 type=module 却用 import | 直接语法错 | 加上 type="module" |
提交 node_modules | 仓库巨大 | .gitignore + npm install |
| 全局随便下脚本不记版本 | 难复现、难升级 | package.json 锁依赖 |
| 默认导出/命名导出混用 | 导入失败 | 看导出方式写对应 import |
| 坚持用 file:// 调试模块 | 被浏览器拦截 | 本地静态服务 |
自测三问
- 模块化主要解决什么问题?
export和import的关系?命名导出与默认导出写法差在哪?npm install通常根据什么文件安装?npm run dev又读哪里?
小结一句话
用 export/import 拆分代码;用 type="module" 在浏览器启用;用 package.json + npm 管理第三方零件与脚本。
延伸
- MDN:JavaScript 模块
- Node.js:包与模块导览(混脸熟即可)
- 下一课:错误处理与调试。