返回笔记列表
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/

一、这一节的目标

  1. 理解"为什么要模块":代码大了以后的组织问题
  2. 掌握 export / import 全套语法(命名导出、默认导出)
  3. 理解模块作用域:模块内的变量外界看不见
  4. 知道 type="module" 的三个特性
  5. 完成多文件小工具——m00 l5 练习的进阶版,也是 React 项目结构的预演

二、为什么需要模块

一个 HTML 文件塞 2000 行 <script> 的痛:

  1. 全局污染:所有变量挤在同一个全局命名空间,var list 互相覆盖
  2. 依赖靠人肉<script> 标签的顺序就是依赖顺序,错一行全崩
  3. 没法复用:想把"坐标转换"函数给别的项目用?复制粘贴

ES Module(ESM)的解法:每个文件是独立模块,模块内变量默认外界不可见,你主动 export 什么,别人才 import 得到什么。依赖关系写在代码里,工具(Vite/Webpack)自动解析顺序。

m00 l5 练习你早已见过:
index.html → <script type="module" src="/src/main.js">
main.jsimport { setupCounter } from './counter.js'
counter.jsexport 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>
  1. 自动严格模式:模块内等价于开着 'use strict'
  2. 模块作用域:顶层变量不再进全局(window 上找不到)
  3. 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/ 三个模块,全部带注释)

步骤:

  1. 用 Live Server 打开 index.html(双击会因 file:// 协议报 CORS——这本身就是要体会的一课)
  2. 功能:输入两城市坐标 → 算球面距离(haversine,geo.js 已实现)→ 格式化输出(format.js)
  3. 读三个模块的 import/export 关系,画出依赖图
  4. 完成 4 个 TODO:main.js 补 distance 计算接线、format.js 加一个 formatDistance(km)(自动选 m/km 单位)、geo.js 导出新的 bearing() 方位角函数并在页面显示、故意删掉一个 export 看报错长什么样

通关标准:

  • 理解并通过 Live Server 跑通(能解释为什么双击打开会 CORS 报错)
  • 武汉→北京距离约 1100~1200km(算错了查坐标顺序:GeoJSON 是 [lng, lat])
  • 能说出"依赖方向向下"为什么重要

七、自测题

  1. 命名导出和默认导出在 import 写法上的区别?
  2. import { degToRad } from 'geo.js' 错在哪?
  3. 模块内的顶层变量会挂到 window 上吗?
  4. type="module" 自带哪三个行为?
  5. 双击 HTML 打开模块化页面报 CORS 错误,原因与解法?

参考答案

  1. 命名导出要花括号且名字严格一致(可 as 改名);默认导出不带花括号、名字任意。
  2. 路径必须以 ./ 开头:应为 './geo.js'
  3. 不会——模块作用域隔离(除非你手动 window.x = ...)。
  4. 严格模式、模块作用域、defer 执行时机。
  5. file:// 协议下模块的跨文件加载被浏览器安全策略禁止;解法:用 Live Server / Vite 等本地 HTTP 服务器访问。

八、阶段一 · JS 部分完结 🎉

第 09~18 节全部完成。你现在已经具备:类型与流程 → 函数与数据 → DOM 与事件 → 异步与网络 → 模块化的完整地基。

收尾动作:

  1. 回头确认 13~18 的练习全部完成(13 的数据流水线和 17 的天气页是综合项目的直接铺垫)
  2. 综合项目 C-02b:地震数据面板 TS 版——不急,等第 19~23 节(TypeScript)学完再做它
  3. 进入 第 19 节:为什么需要 TypeScript
TAGSweb