LESSON 4.1 / MODERN FRONTEND

模块化:让依赖关系变得明确

项目变大后,脚本顺序和全局变量会制造隐形依赖。import / export 让每个文件明确说出自己需要和提供什么。

预计阅读15–20 分钟
完成结果看到 import 就能追踪功能从哪里来,看到 export 就知道模块提供什么。
本课目标
  • 看到 import 就能追踪功能从哪里来,看到 export 就知道模块提供什么。
  • 全局污染、暗依赖与加载顺序
  • 把三个互相依赖的脚本改造成显式模块,并从一个入口调用。
开始之前
  • 完成 3.5「第一次把网页发布到公网」
01

FOUNDATION

必须理解

项目变大后,脚本顺序和全局变量会制造隐形依赖。import / export 让每个文件明确说出自己需要和提供什么。

模块化让文件明确声明自己提供什么、依赖什么。项目变大后,显式依赖比靠脚本顺序和全局变量更容易理解、测试和让 AI 安全修改。

先建立整体直觉

模块像插座标准:每个设备说明输入和输出,就能组合;如果所有电线都藏在墙里互相缠绕,换一个灯泡都可能影响整栋楼。

贯穿本课的实际场景

任务应用把任务数据操作放在 task-store.ts,把任务列表显示放在 task-list.ts,入口 main.ts 负责把两者连接。

概念 1

全局污染、暗依赖与加载顺序

先用白话理解

全局变量像放在公共桌上的东西,谁都能拿、谁都能改,时间一长就不知道是谁动过。模块会把内部细节收好,只把真正需要的能力 export 出去。

基础概念

全局污染是不同代码把名称和数据放进共同作用域,任何地方都可能读取或修改;暗依赖则是代码依赖某个加载顺序或外部变量,却没有明确写出来。

进一步理解

项目小时,全局变量看起来方便;文件变多后,同名覆盖、初始化顺序和意外修改会让行为取决于“谁先运行”。这种关系无法从单个文件的开头看见。

模块拥有自己的作用域,并用 import/export 声明边界。工具因此能分析依赖、发现缺失并只打包真正使用的代码。

放进实际场景

任务列表不再偷偷读取 window.tasks,而是明确导入 task-store 提供的读取函数;测试时也能替换数据实现。

容易混淆的地方

把变量改成不导出只能减少全局暴露,若模块仍直接依赖浏览器中某个神秘对象,暗依赖依然存在。

这一小节记住:让每份代码明确说出需要什么、提供什么,不再依赖碰巧的加载顺序。

概念 2

ES Modules 的 import、export 和入口模块

先用白话理解

export 是“我能提供这些”,import 是“我需要使用这些”。入口文件负责把模块接起来并启动应用,不应该顺便装下全部业务代码。

基础概念

ES Modules 是 JavaScript 的标准模块系统。export 声明模块公开的值,import 按路径取得这些值,入口模块负责连接依赖并启动应用。

进一步理解

具名导出适合一个模块提供多个清晰能力,默认导出适合表示该文件唯一主要值。导入路径必须指向正确文件,浏览器直接运行时还需使用 module 脚本。

模块依赖形成有方向的图。入口位于图的起点,但业务逻辑应留在各自模块;入口只组合配置、数据和界面。循环依赖会让初始化顺序变得难懂。

放进实际场景

main.ts 导入 `addTask` 和 `renderTaskList`,绑定表单事件;数据模块不知道页面细节,列表模块也不负责生成 id。

容易混淆的地方

import 不是复制代码,而是建立对模块导出值的依赖;模块文件也不等于组件,组件只是模块可能导出的一类值。

这一小节记住:入口负责组装,模块通过公开接口合作。

概念 3

第三方库与本地模块的边界

先用白话理解

边界最好跟着职责走。界面只关心怎么显示任务,数据模块只关心任务怎么保存;以后替换其中一层时,另一层就不用跟着重写。

基础概念

第三方库是项目外部维护、通过包管理器安装的模块;本地模块由项目自身拥有。边界决定升级风险、替换成本和哪些细节可以依赖。

进一步理解

业务代码最好通过少量适配层接触复杂第三方接口,避免几十个组件直接绑定同一个库的内部写法。升级或替换时,变化便能集中处理。

本地模块也要有边界。界面模块不应跨层导入数据库实现,数据模块不应直接操作 DOM;依赖方向应从高层需求指向稳定接口。

放进实际场景

日期格式库由 `formatTaskDate` 适配函数包装,任务卡只接收已经格式化的文本,不关心库版本与时区参数。

容易混淆的地方

封装不是给每个第三方函数再起一个同义名称。只有当适配业务语义、隔离变化或统一错误时才有价值。

这一小节记住:依赖可以使用,但要把变化限制在清楚边界内。

显式导入与导出
运行前先确认
  • 项目使用支持 ES Modules 的浏览器或构建工具。
  • 两个示例片段分别位于 task-store.ts 与 main.ts。
// task-store.ts
export function addTask(title: string) {
  return { id: crypto.randomUUID(), title, done: false };
}

// main.ts
import { addTask } from "./task-store";
这段代码在做什么

使用方只依赖公开函数,不需要知道模块内部如何生成任务。

  1. task-store 明确导出 addTask,内部使用 randomUUID 生成标识。
  2. main 通过相对路径导入函数,只依赖它的参数与返回结果。
  3. 以后 task-store 改用 API 时,只要公开契约不变,main 不必知道内部细节。
你应该观察到

调用 addTask('阅读文档') 会得到含唯一 id、标题和未完成状态的对象;导入路径错误时,构建工具会指出找不到模块。

02

AI COLLABORATION

AI 如何参与

让 AI 画出模块依赖图,并说明新增依赖为什么应该放在这一层。

推荐协作顺序

  1. 1

    先让 AI 根据现有函数画出谁调用谁,不急着拆文件。

  2. 2

    确认数据、界面和入口三块职责后,再逐块迁移。

  3. 3

    迁移完成后检查 import 路径和浏览器控制台,确保没有循环依赖。

可直接使用的 Prompt
请把一个包含 tasks 数组、addTask 函数、renderTasks 函数和点击事件的单文件脚本拆成 ES Modules。先画依赖图,再给 task-store.js、task-list.js、main.js 的最小代码。解释每个 import/export,避免循环依赖。
应该得到什么

一个单向、可追踪的依赖图:入口调用数据与界面模块,数据层不反向依赖页面。

人工检查清单

  • 确认文件路径带正确扩展名、script 使用 type=module,并且没有重新制造全局变量。
  • 让 AI 画出 import 方向并标出循环依赖、跨层引用和隐含全局变量。
  • 运行模块入口并故意改错一次导入路径,确认能从错误信息追到真实文件。
03

COMMON TRAPS

常见误区

错误不是需要隐藏的失败,而是帮助你看清系统边界的证据。下面三类问题在 AI 辅助学习中最常出现。

误区 1

为了模块化而切得太碎

你会看到
一个简单按钮要跳过五个文件才能看懂。
为什么发生
一个万能 utils 会吸收互不相关的职责,调用者只知道“都在这里”,最终形成新的全局依赖中心。
怎样纠正
按职责和变化原因拆分,不按代码行数拆分。
误区 2

模块之间互相调用

你会看到
A 导入 B,B 又导入 A,启动时得到未初始化的值。
为什么发生
跨越模块公开边界引用内部文件,短期少写一层,长期会让任何目录调整都扩散到全项目。
怎样纠正
把共同依赖上移到第三个模块,保持数据流方向清楚。
误区 3

换成 import 后忘记入口设置

你会看到
浏览器提示 import 不能在普通脚本中使用。
为什么发生
循环依赖使模块初始化互相等待,某些值在使用时尚未建立,错误常随打包方式变化。
怎样纠正
确认 script 使用 type=module,并检查相对路径和扩展名。
04

HANDS-ON

动手任务

把三个互相依赖的脚本改造成显式模块,并从一个入口调用。

准备条件
  • 完成 3.5「第一次把网页发布到公网」

跟着做

  1. 01

    列出当前 script.js 中的数据、渲染与事件三类职责。

  2. 02

    创建 task-store.js,导出读取和新增任务的方法。

  3. 03

    创建 task-list.js,只接收任务并返回或更新列表。

  4. 04

    创建 main.js 导入两者并绑定按钮事件。

  5. 05

    把 HTML 脚本改为 type=module,验证功能与错误控制台。

完成标志

看到 import 就能追踪功能从哪里来,看到 export 就知道模块提供什么。

加餐挑战

加入 removeTask,但保持数据模块不知道 DOM 元素的存在。

离开本课前,自问四件事

  • 全局污染与暗依赖分别是什么,为什么项目变大后更危险?
  • import、export 和入口模块各自承担什么职责?
  • 我能否画出任务应用三个模块的依赖方向?
  • 什么时候值得为第三方库建立适配边界,什么时候只是多余包装?
完成本课了吗?

确认完成后,会同步更新学习中心的课程学习进度。