十年前,我用 11 行 Node.js 代码,拼出了人生第一个网页。
那时没有 Vue,没有 React,甚至没有 import。
只有一串 require('http'),和一段在 server.js 里硬生生拼出来的 HTML 字符串。
我以为这就是 Web 开发的全部。
后来,我学会了前后端分离,用 fetch 拉数据、用 innerHTML 撑起整个页面——
自以为终于“现代化”了,却每天在 DOM 的泥潭里越陷越深。
直到某天,我看到同事用 `` 写出一个会自动更新的表格,
而我还在手动清空 tbody、再一行行插 <tr>……
那一刻我才明白:技术演进从不等待怀旧的人。
今天,我不讲框架原理,也不卷源码解析。
我只想带你回溯这十年 Web 前端的三次“生死跃迁”——
从手工作坊,到流水线,再到智能工厂。
如果你还在用“能跑就行”的逻辑写代码,
这篇文章,可能会改变你对“前端工程师”这个职业的认知。
🧱 阶段一:手工作坊时代 —— 用 11 行代码撑起整个世界
“那是一个连‘前端工程师’这个词都还没被发明出来的年代。”
2014 年前后,Node.js 正在席卷全栈开发圈。我们这些刚入行的 “切图仔”,第一次听说“JavaScript 不只能跑在浏览器里”时,眼睛都亮了。
于是,我写下了人生第一个 Web 服务:
const http = require("http");
const url = require("url"); // url
// 数据部分 不重要
const users = [
{
id: 1,
name: '张三',
email: '123@qq.com'
},
{
id: 2,
name: '李四',
email: '1232@qq.com'
},
{
id: 3,
name: '王五',
email: '121@qq.com'
},
]
function generateUserHTML(users) {
const userRows = users.map(user => `
<tr>
<td>${user.id}</td>
<td>${user.name}</td>
<td>${user.email}</td>
</tr>
`).join('');
return `
User List
table { width: 100%; border-collapse: collapse; margin-top: 20px; }
th, td { border: 1px solid #ccc; padding: 8px; text-align: left; }
th { background-color: #f4f4f4; }
<h1>Users</h1>
<table>
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Email</th>
</tr>
</thead>
<tbody>
${userRows}
</tbody>
</table>
`
}
const server = http.createServer((req, res) => {
// url.parse(req.url, true):把请求的 URL(如 /users?name=shu)解析成结构化对象
const parsedUrl = url.parse(req.url, true);
// console.log(parsedUrl);
if (parsedUrl.pathname === '/'
|| parsedUrl.pathname === '/users') {
res.statusCode = 200; // 响应头 状态码
res.setHeader('Content-Type', 'text/html;charset=utf-8');
const html = generateUserHTML(users);
res.end(html);
} else {
res.statusCode = 404;
res.setHeader('Content-Type', "text/plain")
res.end('Not Found');
}
})
server.listen(1314, () => {
console.log('Server is running on port 1314');
});
看,就这一个能跑的网站诞生了。
没有 webpack,没有 npm run dev,甚至没有 CSS 文件——所有样式都得塞进 `` 标签里。
数据?直接写死在字符串里。
路由?靠 if (req.url === '/users') 硬判断。
模块化?require 是唯一的光,但没人敢在大型项目里只用一个 server.js。
可当时,我们都觉得这很酷。
因为页面一刷新,就是完整的 HTML。SEO 友好、首屏快、逻辑集中——一切看起来“刚刚好”。
但问题很快来了。
当产品说:“加个用户新增功能吧。”
你得在同一个文件里塞进 POST 解析、表单处理、HTML 拼接、错误提示……
当 UI 改版,你得手动改三处拼接字符串;
当同事接手你的代码,他盯着满屏反引号和 ${},沉默了十分钟。
这不是开发,这是用代码织毛衣——每一针都亲手缝,每一行都带着体温,但也脆弱得一扯就散。
更致命的是:前后端完全耦合。
后端改个字段名,前端页面直接崩;前端想换个布局,后端得重写 HTML 模板。
团队协作?基本靠吼。
可那时我们别无选择。
因为 JavaScript 在浏览器里连模块系统都没有(ES6 的 import 要到 2015 年才标准化),
而 Node.js 虽然用 CommonJS(require)解决了服务端模块化,
但前端仍然是蛮荒之地。
于是,一场革命悄然酝酿——
人们开始问:能不能让浏览器自己去拿数据,而不是每次都让服务器把整张脸画好再送过来?
这个问题,催生了下一个时代。
⚙️ 阶段二:流水线时代 —— 我以为自由了,其实只是换了牢笼
“2016 年,我第一次用
fetch从接口拿到数据时,感觉自己像个黑客。”
那时,前端圈正掀起一场静默革命:前后端分离。
后端只负责提供 JSON API,前端用 HTML + CSS + JavaScript 自己“组装”页面。
我们管这叫“解耦”——听起来像某种高级哲学。
我兴奋地搭起一个最简项目:
- 后端用
json-server,30 秒启动一个 RESTful API; - 前端就是一个
index.html,里面嵌了一段原生 JavaScript:
User List
table { width: 100%; border-collapse: collapse; margin-top: 20px; }
th, td { border: 1px solid #ccc; padding: 8px; text-align: left; }
th { background-color: #f4f4f4; }
<h1>Users</h1>
<table>
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Email</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
// 后端接口准备好了
// DOM 编程
fetch('http://localhost:3000/users')
.then(res => res.json())
.then(data => {
console.log(data);
const tbody = document.querySelector('tbody');
tbody.innerHTML = data.map( user => `
<tr>
<td>${user.id}</td>
<td>${user.name}</td>
<td>${user.email}</td>
</tr>
`).join('');
})
看!页面内容不再由服务器生成,而是浏览器自己“拉”回来再拼装。
后端改字段?只要 API 不变,前端不动。
UI 调整?直接改 HTML 和 JS,不用碰后端代码。
团队协作?前端写页面,后端写接口,各干各的——终于不用互相甩锅了!
那一刻,我以为 Web 开发进入了“现代化”。
但很快,现实给了我一记 DOM 操作的耳光。
当需求变成:“点击用户名,弹出编辑框,保存后实时更新表格”,
我不得不:
- 手动监听每个
<td>的点击事件; - 动态创建 `` 元素插入 DOM;
- 拦截回车键,发起 PUT 请求;
- 请求成功后,再手动找到对应
<tr>,替换文本内容……
每一次状态变更,都是一场与 DOM 的肉搏战。
更可怕的是:页面越复杂,JS 越像一团意大利面。
数据在哪?在 data 变量里。
视图在哪?在 innerHTML 里。
两者之间?靠一堆字符串拼接和 querySelector 维系。
一旦数据变了,你必须记得去“同步”视图——漏掉一处,页面就错乱。
我们管这叫“手动同步”,但本质上,是用人脑维护状态一致性。
而人,是最不可靠的状态管理器。
讽刺的是,虽然我们把“前后端分离”当作进步,
却在前端内部,重新制造了逻辑与视图的紧耦合——
只不过这次,耦合发生在 JavaScript 和 HTML 之间。
更别提那些没有 Promise 的老项目,满屏的回调地狱;
或者 IE 兼容问题,让 fetch 根本不能用,还得引入 axios 或自己封装 XHR。
我们挣脱了服务端的枷锁,却亲手给自己戴上了 DOM 的镣铐。
可即便如此,这个阶段仍是必要的。
因为它教会了我们一个真理:UI 应该由数据驱动,而不是由字符串拼接驱动。
只是我们还不知道,如何优雅地实现它。
直到某天,有人问:“如果数据变了,视图能自动更新呢?”
这个问题,点燃了下一场革命。
🌐 阶段三:智能工厂时代 —— 数据一变,世界自动更新
“2023 年,我只写了 8 行代码,就实现了一个会呼吸的页面。”
没有 innerHTML,没有事件代理,甚至没有显式的 DOM 操作。
只有:
import {ref} from 'vue';
const users = ref([
{ id: 1, name: '张三',email: 'john@example.com'
},
{ id: 2,name: '李四',email: 'jane@example.com'},
]);
onMounted(()=>{
fetch('https://localhost:3000/users')
.then(res => res.json())
.then(data => {
users.value = data;
})
})
<table>
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Email</th>
</tr>
</thead>
<tbody>
<tr>
<td>{{ user.id }}</td>
<td>{{ user.name }}</td>
<td>{{ user.email }}</td>
</tr>
</tbody>
</table>
然后,当我执行 setTimeout(()=>{ users.value.push({ id: 3, name: '王五', email: 'zhangsan@example.com' },5000),
表格五秒后多了一行。
没有清空、没有拼接、没有
querySelector——
仿佛页面天生就知道:“数据变了,我该更新了。”
那一刻,我忽然理解了什么叫 “响应式” 。
这不是魔法,而是一套精密的依赖追踪系统:
Vue 在编译时分析模板,运行时建立“数据 → 视图”的映射关系。
你只管改数据,框架负责更新最小必要 DOM。
状态即 UI,UI 即状态。
而这,只是冰山一角。
再看入口文件:
// main.js
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
短短三行,一个应用启动。
背后是 Vite 的闪电构建:
- 无需打包,原生 ESM 直接加载
.vue文件; - 修改代码,浏览器毫秒级热更新(HMR) ,状态不丢;
- 开发服务器启动快到你以为它没跑起来。
对比十年前那个改一行 HTML 就要 Ctrl+C + node server.js 的时代,
这简直是科幻。
更别说 `` 让组件逻辑极度简洁,
ref / reactive 让状态管理清晰可测,
单文件组件(.vue)把模板、逻辑、样式封装成可复用的“乐高积木”。
我们终于不再“操作 DOM”,而是“描述 UI”。
大型项目?拆成几十个组件,各自独立开发、测试、复用。
团队协作?只要约定好 props 和 emit,前后端甚至能并行狂奔。
维护成本?改一个组件,天塌不下来——除非你又偷偷用了 globalThis 😅。
更重要的是,开发者的心智负担被大幅卸载。
你不再需要在脑内维护一张“点击按钮 → 找 tbody → 清空 → 重拼 HTML”的操作流程图,
而是只需回答一个简单问题: “在当前状态下,UI 应该是什么样子?”
剩下的脏活——DOM 操作、依赖追踪、更新优化——框架默默扛下。
而你,终于可以真正聚焦于业务本身
🧭 回望来路:三次跃迁的本质
从 阶段一 的服务端硬拼 HTML,
到 阶段二 的前端手动同步 DOM,
再到 阶段三 的声明式响应式 UI——
Web 开发的演进,从来不是为了“用新工具替代旧工具”,
而是为了把人从重复、易错、低效的机械劳动中解放出来。
- SSR 时代,我们在服务端“画脸”;
- 前后端分离时代,我们在浏览器里“拼脸”;
- 而今天,我们只需“描述脸应该是什么样”,
框架自动把它画出来,并且随情绪实时变化。
这,才是现代前端框架真正的价值:
不是让你写更少的代码,而是让你思考更高维的问题。
💡 结语
技术不会淘汰语言,但会淘汰思维
今天,依然有人嘲笑 Vue/React 是“过度封装”,
说“原生 JS 才是最高效的”。
但我想说:
高效,从来不是指工具的效率,而是人的效率。
当你的同事还在为 innerHTML 的 XSS 漏洞提心吊胆,
你已经用组件隔离了风险;
当别人在调试第 17 层回调嵌套,
你用 async/await 写出了线性逻辑;
当整个行业都在谈论微前端、低代码、AI 编程,
你早已习惯“声明意图,而非操作步骤”。
技术演进从不等待怀旧的人——但它永远欢迎清醒的前行者。
所以,别问“要不要学框架”。
问问自己:
你是想一辈子当“人肉 API + DOM 拼装工”,
还是成为那个定义 UI 与数据关系的“系统设计师”?
答案,就在你下一行代码里。
你在哪个阶段卡得最久?
评论区聊聊你的“技术觉醒时刻”
如果这篇文章让你想起某个深夜 debug 的自己——转发给那个还在手搓 HTML 的朋友吧!