📖 课程第一章:混乱的起源 —— 当项目失去秩序
副标题:从手动管理 <script> 标签到理解模块化的必要性
1. 课程目标 (Learning Objectives)
完成本课后,你将能够:
- 复现痛点:在没有构建工具的情况下,手动维护一个多文件依赖的项目,体验“依赖地狱”。
- 理解本质:深刻理解浏览器原生脚本加载机制与模块化需求的矛盾。
- 定义工程化:用一句话清晰定义前端工程化的核心价值(不仅仅是打包,更是依赖管理和开发体验)。
- 产出物:一个故意写得“很烂”的 Vanilla JS 项目代码库,作为后续课程的“重构靶子”。
2. 核心场景:一个“即将崩溃”的待办列表 (Todo List)
我们将创建一个简单的 Todo List,但故意采用最原始、最手工的方式来组织代码。
🛠️ 动手实践 (Hands-on Lab)
请创建一个文件夹 chapter-1-chaos,并建立以下文件结构:
chapter-1-chaos/
├── index.html
├── js/
│ ├── utils.js // 工具函数
│ ├── store.js // 数据存储
│ ├── renderer.js // 页面渲染
│ └── app.js // 入口逻辑
└── css/
└── style.css
第一步:编写业务逻辑 (故意制造依赖耦合)
js/utils.js (基础工具)
// 注意:这里没有 export,我们假设它是全局变量
function generateId() {
return Date.now().toString(36) + Math.random().toString(36).substr(2);
}
function saveToLocal(data) {
localStorage.setItem('todos', JSON.stringify(data));
}
js/store.js (依赖 utils)
// 问题预埋:如果 utils.js 没加载,这里会报错 "generateId is not defined"
const Store = {
todos: [],
add(text) {
const todo = { id: generateId(), text, completed: false }; // 依赖全局函数
this.todos.push(todo);
saveToLocal(this.todos); // 依赖全局函数
return todo;
},
init() {
const data = localStorage.getItem('todos');
this.todos = data ? JSON.parse(data) : [];
}
};
js/renderer.js (依赖 store)
// 问题预埋:依赖全局变量 Store
const Renderer = {
render() {
const listEl = document.getElementById('todo-list');
listEl.innerHTML = '';
Store.todos.forEach(todo => { // 依赖全局对象
const li = document.createElement('li');
li.textContent = todo.text;
listEl.appendChild(li);
});
}
};
js/app.js (入口)
// 初始化
Store.init();
Renderer.render();
// 绑定事件 (为了简单直接写在这里)
document.getElementById('add-btn').addEventListener('click', () => {
const input = document.getElementById('todo-input');
if(input.value) {
Store.add(input.value);
Renderer.render();
input.value = '';
}
});
console.log("App Initialized");
第二步:在 HTML 中手动编排 (痛苦的根源)
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>混沌的 Todo List</title>
<!-- 样式先不管 -->
</head>
<body>
<h1>我的待办事项 (原始版)</h1>
<input type="text" id="todo-input" placeholder="输入任务...">
<button id="add-btn">添加</button>
<ul id="todo-list"></ul>
<!--
🔴 关键痛点时刻:
你必须人工保证脚本的加载顺序!
如果不小心把 app.js 放在 utils.js 前面,程序直接崩溃。
如果新增一个 helper.js 被 store.js 依赖,你得记得把它插到中间。
-->
<script src="js/utils.js"></script>
<script src="js/store.js"></script>
<script src="js/renderer.js"></script>
<script src="js/app.js"></script>
</body>
</html>
3. 深度剖析:我们遇到了什么问题?
现在,运行这个 HTML 文件(直接用浏览器打开或简单的 Live Server)。虽然它能跑,但请思考以下工程化危机:
❌ 危机一:脆弱的依赖顺序 (Dependency Order)
- 现象:如果你在
index.html中不小心把<script src="js/store.js">放到了utils.js前面。 - 后果:控制台报错
Uncaught ReferenceError: generateId is not defined,整个应用白屏。 - 工程化视角:在大型项目中,文件可能有上百个,人工维护拓扑排序(Topological Sort)是不可能的。构建工具的第一个作用:自动分析依赖图,解决加载顺序。
❌ 危机二:全局污染 (Global Namespace Pollution)
- 现象:
generateId,Store,Renderer全部挂载在全局window对象上(隐式或显式)。 - 后果:如果引入第三方库(比如 jQuery 或某个 analytics 脚本),它们也定义了一个
Store变量,你的代码瞬间被覆盖,Bug 难以追踪。 - 工程化视角:我们需要作用域隔离。这就是模块化(CommonJS/ESM)诞生的原因。
❌ 危机三:难以维护与重构
-
现象:如果你想把
generateId移动到一个新的helpers/id-generator.js文件中。 -
后果:
- 新建文件。
- 剪切代码。
- 回到
index.html修改<script>标签顺序。 - 检查所有引用了
generateId的文件,确保它们都在新文件之后加载。
-
工程化视角:这种高耦合让重构变得极其危险。我们需要静态分析能力,让工具告诉我们在哪里引用了什么。
❌ 危机四:网络性能灾难
- 现象:浏览器每遇到一个
<script>标签,就要发起一次 HTTP 请求(HTTP/1.1 下尤其严重,即使 HTTP/2 也有开销)。 - 后果:10 个 JS 文件 = 10 次请求 + 10 次解析阻塞。首屏加载极慢。
- 工程化视角:我们需要打包 (Bundling) ,将多个小文件合并为一个大文件(或按需分割),减少请求次数。
4. 理论升华:什么是前端工程化?
通过上面的痛苦体验,我们可以给出一个深刻的定义:
前端工程化,就是利用工具链和规范化流程,将手工作坊式的代码编写模式(手动管理依赖、全局变量、原始文件),转化为工业化的生产模式(自动依赖分析、作用域隔离、资源优化),以解决复杂度、协作和性能问题。
它的核心不是 Webpack 或 Vite 这些工具,而是解决上述四个危机的思维模式。
5. 课后作业 (Homework)
为了巩固记忆,请完成以下挑战:
-
制造崩溃:故意打乱
index.html中的 script 顺序,截图报错信息,感受那种“不知道哪里错了”的无助感。 -
模拟扩展:尝试新增一个
validator.js文件,里面有一个validate(text)函数,并在store.js中使用它。- 你需要修改几个文件?
- 如果你忘了在 HTML 里加
validator.js,会发生什么? - 如果你加错了位置,会发生什么?
-
思考题:如果这个项目从 5 个文件变成 500 个文件,你觉得现在的开发模式还能维持吗?你会怎么解决?(写下你的直觉方案,不用管对错,这是下节课的伏笔)。
6. 下节课预告
“既然手动管理顺序这么痛苦,那如果我们让 JavaScript 自己告诉浏览器谁依赖谁呢?
下节课,我们将穿越回 2009-2015 年,深入模块化战争:从 CommonJS 到 AMD,再到 ESM 的最终胜利。我们将亲手实现一个简易的模块加载器。”
💡 给你的执行建议
- 创建仓库:现在就在 GitHub/Gitee 上创建一个名为
frontend-engineering-from-scratch的仓库。 - 提交代码:把刚才那个“混乱版”的 Todo List 代码提交上去,Commit 信息写:
feat(chapter-1): 初始化的混乱版本,体验无工程化的痛苦。