01 · NOTES / WebGIS 开发
第 18 节 · 模块化(ES Module)
2026 年 9 月 1 日
第 18 节 · 模块化(ES Module)
📌 版本信息:基于 ECMAScript Modules(浏览器原生支持多年;2026-08-29 核对) 📚 来源:现代 JS 教程 · 模块简介 | MDN JavaScript modules | 本地教材
resources/01-web-basics/modern-js-tutorial/
一、这一节的目标
- 理解"为什么要模块":代码大了以后的组织问题
- 掌握
export/import全套语法(命名导出、默认导出) - 理解模块作用域:模块内的变量外界看不见
- 知道
type="module"的三个特性 - 完成多文件小工具——m00 l5 练习的进阶版,也是 React 项目结构的预演
二、为什么需要模块
一个 HTML 文件塞 2000 行 <script> 的痛:
- 全局污染:所有变量挤在同一个全局命名空间,
var list互相覆盖 - 依赖靠人肉:
<script>标签的顺序就是依赖顺序,错一行全崩 - 没法复用:想把"坐标转换"函数给别的项目用?复制粘贴
ES Module(ESM)的解法:每个文件是独立模块,模块内变量默认外界不可见,你主动 export 什么,别人才 import 得到什么。依赖关系写在代码里,工具(Vite/Webpack)自动解析顺序。
m00 l5 练习你早已见过:
index.html → <script type="module" src="/src/main.js">
main.js → import { setupCounter } from './counter.js'
counter.js → export function setupCounter(...) {...}
三、export / import 语法全集
// ═════ geo.js —— 命名导出(一文件多导出,主力写法) ═════
export function degToRad(deg) { return deg * (Math.PI / 180); }
export const EARTH_RADIUS = 6371; // km
// 也可以统一在底部导出
function haversine(lat1, lng1, lat2, lng2) { /* 两点球面距离 */ }
export { haversine };
// ═════ format.js —— 默认导出(一文件一个,"这个文件的主角") ═════
export default function formatCoord(lat, lng, digits = 4) {
return `${lat.toFixed(digits)}N, ${lng.toFixed(digits)}E`;
}
// ═════ main.js —— 各种导入姿势 ═════
import formatCoord from './format.js'; // 默认导出:不带花括号,名字随意
import { degToRad, EARTH_RADIUS } from './geo.js'; // 命名导出:花括号 + 名字必须一致
import { haversine as hav } from './geo.js'; // as 改名
import * as geo from './geo.js'; // 整体导入:geo.degToRad(...) 用
规则速记:
- 命名导出导入要花括号、名字严格一致;默认导出不带花括号、随便起名
- 一个模块可有多个命名导出、至多一个默认导出
- 路径必须以
./..//开头(相对/根路径),光写文件名是不行的
四、type="module" 的三个特性
<script type="module" src="./main.js"></script>
- 自动严格模式:模块内等价于开着
'use strict' - 模块作用域:顶层变量不再进全局(window 上找不到)
- defer 行为:自动等 HTML 解析完才执行(不用操心脚本放哪)
⚠️ 必须通过 HTTP 访问:ESM 的跨文件加载在
file://协议(直接双击 HTML)下被浏览器安全策略禁止——你会看到 CORS 报错。必须用 Live Server / Vite 这类本地服务器打开(m00 的工具此刻派上用场)。
五、模块组织思想(给未来铺路)
拆模块的原则:按职责,不按大小。这次练习的三件套就是最经典的前端分层:
18-模块化小工具/
├── index.html ← 壳
└── src/
├── main.js ← 接线层:DOM 事件 → 调用逻辑 → 渲染结果
├── geo.js ← 领域层:GIS 计算逻辑(纯函数)
└── format.js ← 工具层:格式化(纯函数)
main.js是"指挥",geo.js/format.js是"专家"- 依赖方向永远向下:main → geo/format,绝不反向(geo.js 里不许出现 document)
- React 项目(第 03 模块)就是把这个思想放大:组件层 / hooks 层 / utils 层 / api 层
六、动手跟练:18 · 模块化小工具(多文件练习)
配套文件夹:02-web-basics/examples/18-模块化小工具/(index.html + src/ 三个模块,全部带注释)
步骤:
- 用 Live Server 打开 index.html(双击会因 file:// 协议报 CORS——这本身就是要体会的一课)
- 功能:输入两城市坐标 → 算球面距离(haversine,geo.js 已实现)→ 格式化输出(format.js)
- 读三个模块的 import/export 关系,画出依赖图
- 完成 4 个 TODO:main.js 补 distance 计算接线、format.js 加一个
formatDistance(km)(自动选 m/km 单位)、geo.js 导出新的bearing()方位角函数并在页面显示、故意删掉一个 export 看报错长什么样
通关标准:
- 理解并通过 Live Server 跑通(能解释为什么双击打开会 CORS 报错)
- 武汉→北京距离约 1100~1200km(算错了查坐标顺序:GeoJSON 是 [lng, lat])
- 能说出"依赖方向向下"为什么重要
七、自测题
- 命名导出和默认导出在 import 写法上的区别?
import { degToRad } from 'geo.js'错在哪?- 模块内的顶层变量会挂到 window 上吗?
type="module"自带哪三个行为?- 双击 HTML 打开模块化页面报 CORS 错误,原因与解法?
参考答案
- 命名导出要花括号且名字严格一致(可 as 改名);默认导出不带花括号、名字任意。
- 路径必须以
./开头:应为'./geo.js'。 - 不会——模块作用域隔离(除非你手动
window.x = ...)。 - 严格模式、模块作用域、defer 执行时机。
- file:// 协议下模块的跨文件加载被浏览器安全策略禁止;解法:用 Live Server / Vite 等本地 HTTP 服务器访问。
八、阶段一 · JS 部分完结 🎉
第 09~18 节全部完成。你现在已经具备:类型与流程 → 函数与数据 → DOM 与事件 → 异步与网络 → 模块化的完整地基。
收尾动作:
- 回头确认 13~18 的练习全部完成(13 的数据流水线和 17 的天气页是综合项目的直接铺垫)
- 做 综合项目 C-02b:地震数据面板 TS 版——不急,等第 19~23 节(TypeScript)学完再做它
- 进入 第 19 节:为什么需要 TypeScript
TAGSweb