LESSON 4.1 / MODERN FRONTEND

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

当脚本增多时,模块让每个文件明确声明自己提供什么、依赖什么。这样可以沿 import 找到来源,不再依赖猜测加载顺序。

预计阅读约 25 到 40 分钟
完成结果把任务数据、筛选逻辑和页面入口拆成三个模块,并能画出它们的单向依赖关系。
本课目标
  • 把任务数据、筛选逻辑和页面入口拆成三个模块,并能画出它们的单向依赖关系。
  • 全局污染、暗依赖与加载顺序
  • 验证模块化:让依赖关系变得明确后,应当把任务数据、筛选逻辑和页面入口拆成三个模块,并能画出它们的单向依赖关系,并保留类型检查、生产构建、浏览器行为和请求结果作为可重复检查的依据。
开始之前
  • 完成 3.8「第一次把网页发布到公网」
01

FOUNDATION

必须理解

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

本课依次讲清全局污染、暗依赖与加载顺序、ES Modules 的 import、export 和入口模块和第三方库与本地模块的边界,最后通过“把 HTML 脚本改为 type=module,验证功能与错误控制台”检查学习结果。

先建立整体直觉

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

贯穿本课的实际场景

任务应用把任务数据操作放在 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
这是当前脚本和目标文件树。请先画依赖关系,再给迁移顺序。每个模块写清导入、导出和职责,避免循环依赖,不创建万能 utils。一次只改一个文件,并提供浏览器验收点。
应该得到什么

一个单向、可追踪的依赖图:入口调用数据与界面模块,数据层不反向依赖页面;模块化:让依赖关系变得明确的人工验收必须回到真实页面、请求、终端、测试或数据结果,不能用 AI 的文字说明代替。

人工检查清单

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

COMMON TRAPS

常见误区

下面三类问题会让任务看似完成,却经不起刷新、错误输入或真实环境检查。先看现象,再找原因和修正方法。

误区 1

制造循环依赖

你会看到
两个模块互相导入,初始化值不稳定
为什么发生
模块初始化存在先后顺序,互相等待时可能读到尚未准备好的导出。
怎样纠正
把共享规则移到更低层或重新划分职责
误区 2

建立万能工具文件

你会看到
不相关函数堆在一起,任何改动都牵连它
为什么发生
没有共同职责的函数会持续堆积,任何使用者都可能依赖文件内部细节。
怎样纠正
按业务含义命名模块
误区 3

深层跨边界引用

你会看到
组件绕过公开入口依赖内部文件
为什么发生
调用方依赖了模块内部结构,内部一重组,边界外的代码也会一起损坏。
怎样纠正
为模块保留清楚的公开导出
04

HANDS-ON

动手任务

把任务数据、筛选逻辑和页面入口拆成三个模块,并能画出它们的单向依赖关系。

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

跟着做

  1. 01

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

  2. 02

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

  3. 03

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

  4. 04

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

  5. 05

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

完成标志

把任务数据、筛选逻辑和页面入口拆成三个模块,并能画出它们的单向依赖关系。

加餐挑战

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

离开本课前,自问四件事

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

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