36年回顾:互联网前端发展史
学习计算机科学就是在学习计算机历史
0. 引
0.1 Only screen is real
我们常把互联网比作冰山,在冰山之下,数以万计的交换机/服务器/数据库在轰隆作响
而对于屏幕前的用户,某个拿着手机坐在地铁里的人,或者坐在办公桌前盯着显示器的白领而言,只有屏幕上的像素点是真实的
如果那个”commit”按钮点下去没有反应;如果那个列表滑动起来一卡一卡;如果那个页面在加载时白屏
那么,无论后端的架构多么优雅,算法多么精准,对于用户来说,这就是一个 垃圾软件
在很长一段时间里,前端被戏称为切图仔
在软件开发的鄙视链中,前端曾经往往处于底端
后端工程师认为前端只是写写 HTML 标签、描描样式,不需要懂内存管理,不需要懂并发控制,甚至不需要懂真正的编程语言。
然而,这三十年的历史证明了这种偏见的荒谬。前端开发正在经历计算机科学史上最剧烈,最残酷的演变。我们从最初仅仅是为了展示一篇物理论文,演变到现在要在浏览器这个沙盒里运行 Photoshop、3D 游戏、甚至是 IDE
本篇blog要讲的,不是 API 的流水账,而是这三十年来前端的「三国演义」
观前提醒:本文是以我的个人视角叙述,难免有误,还请各位谅解/多多指出错误。
0.2 原罪
要理解前端这三十年为什么这么闹腾,为什么框架层出不穷,为什么工具链复杂到让人头秃,我们必须回到原点,去审视 Web 诞生的初衷
WWW 在 1990 年诞生时,Tim Berners-Lee 爵士的设计目标非常纯粹:为了让科学家们方便地共享文档
请记住文档这两个字。这是前端所有痛苦和复杂的根源,也是我认为的原罪
- HTML 是用来描述文档结构的
- HTTP 是无状态的协议,以此请求文档,拿完就走,不保持连接
- 浏览器 只是一个文档阅读器
随着商业化的爆发,人类越来越想要更多。我们要的不再是一张静态的纸,我们要的是一个App
- 我们要在这个文档阅读器里做Chat
- 我们要在这里做交易所
- 我们要在这里协同编辑 Excel
- 我们要在这里看 HDR 视频
这就好比要求你在 Microsoft Word 里开发一个 Minecraft
前端这三十年在做的事情,本质上就是:
利用一系列原本为静态文档设计的简陋技术,通过各种奇技淫巧,强行构建出媲美原生操作系统软件的动态应用
- 因为 HTTP 无状态,所以我们发明了 Cookie 和 Session,后来又搞出了 JWT
- 因为 HTML 每次点击都要刷新整页,体验太差,所以我们发明了 AJAX,强行把网页变成了 SPA
- 因为 DOM 操作太慢,所以我们发明了 Virtual DOM,在内存里算好了再去碰真实的 DOM
- 因为 JavaScript 诞生时没有模块系统,所以我们发明了 CommonJS, AMD, UMD, 最终逼出了 ES Modules
前端的演进史,就是一部与浏览器的对抗史。我们在对抗浏览器的限制,对抗网络的延迟,对抗设备的碎片化
0.3 轮回
在读本Blog时,你会发现一个非常有趣的现象:技术的轮回
- Web 1.0:网页是服务端渲染的。PHP/JSP 在服务器把 HTML 拼好,浏览器直接展示。那是静态的极致
- Web 2.0:为了交互体验,我们把逻辑全搬到了浏览器。浏览器成了客户端,服务器只吐 JSON 数据。这是动态的极致
- Now:为了首屏速度和 SEO,我们发现客户端渲染太慢了。于是,Next.js 和 Nuxt 又出现了
人类绕了一圈,似乎回到了原点?
不
当年的静态,是因为我们做不到动态 现在的静态,是因为我们掌握了动态之后,为了极致体验而做出的主动选择
0.4 谁是前端?
在 2000 年,只要会写 <table> 布局和 <font> 标签,你就是前端(那时候其实叫网页设计师)
在 2010 年,只要你会用 jQuery 的 $('#id').click(),你就是前端 现在,你需要会Ts,即时编译,HTTP/3,服务端容器化等等
前端的边界在无限扩张。 向上,我们在侵蚀后端的领地推出Serverless(尽管现在不尽人意)
向下,我们在通过 WebAssembly 调用 C++/Rust 的能力
向外,我们在通过 React Native / Flutter 构建原生App
但无论技术栈如何变迁,有一点我希望阅读本文的前端开发者记住:
我们是离用户最近的人
1. 最初的起点
1.1 蒂姆·伯纳斯-李的拼图
一切的起点,并不宏大。没有风险投资,没有 IPO,甚至没有想改变世界
1989 年,蒂姆·伯纳斯-李(Tim Berners-Lee)在 CERN 工作时,面临着一个非常具体的痛点:物理学家们的论文和数据太难找了。它们分散在不同的计算机上,格式各异,如果你想引用别人的研究,只能手动去查
于是,他提出了一个构想:World Wide Web
为了实现这个构想,他拿出了三块拼图,这三块拼图至今仍是互联网的基石:
- HTML:一种简单的标记语言,用来描述文档的结构(标题、段落、列表)。这里你要注意的是,它只负责结构,不负责好看
- HTTP:一种无状态的传输协议。你请求一个文档,服务器给你,然后连接断开。这种阅后即焚的特性决定了 Web 最初只是一个图书馆,而不是「操作系统」
- **URL **:给世界上的每一个文档分配一个唯一的地址
1990 年圣诞节,他在那台 NeXT 电脑上写出了世界上第一个浏览器和第一个服务器
那时的网页长什么样? Only text 只有白纸黑字和蓝色的超链接。图片还需要弹出一个新窗口才能看
这时候的前端开发,和写word没什么区别。你只需要掌握 <h1>、<p>、<a> 这几个标签就够了。这是一个Read-Only的时代
1.2 Table 与 Spacer GIF
随着 Mosaic 浏览器的发布(第一个广泛流行的图形化浏览器),图片终于可以和文字混排了。商业公司开始入驻互联网,设计师们带着平面设计的思维来了
炸了
设计师说:我要在这个角落放个 Logo,在大标题下面分三栏排版,右边还要有一个Guide
工程师看着简陋的 HTML 说:把哥们当日本人整呢
当时的 HTML 只有文档流。没有 Float,没有 Position,更没有 Flexbox
怎么办?聪明或者说被逼无奈的开发者们发现了一个漏洞:HTML 里有一个用来展示数据的标签<table>,也就是表格
如果你把边框设为 0 (border="0"),表格线不就看不见了吗?
如果你在表格里套表格,不就可以切分出复杂的版块了吗?
于是,表格布局时代开始了
- 切图仔诞生:设计师在 Photoshop 里把一张精美的网页设计图切成几十个小豆腐块,然后前端工程师用一个巨大的
<table>把它们拼回去 - 嵌套地狱:为了实现一个圆角框,你可能需要一个 3x3 的表格,四个角放圆角图片,中间放内容。如果内容里还有布局,那就再套一个表格。代码看起来像是一个巨大的迷宫
- Spacer GIF:这是那个时代最伟大的「黑客技术」
- 表格单元格有时会塌陷,或者你想要精确的 10px 间距,怎么办?
- 你制作一个 1x1 像素的透明 GIF 图片
- 然后写上
<img src="spacer.gif" width="10" height="1"> - 你看不到它,但它撑开了空间
这是前端工程史上第一次大规模的为了目的不择手段。虽然代码语义极差,维护极其困难(改一个字可能导致整个表格崩塌),但它让网页第一次有了设计感
1.3 JavaScript 的急就章
1995 年,Netscape 正处于第一次浏览器大战的风口浪尖。他们面临一个巨大的压力:让网页动起来
当时的痛点非常低级:表单验证
那是拨号上网的年代,网速慢得令人发指。用户在网页上填完一个长长的注册表单,点击提交,数据传到服务器,服务器发现用户名为空,再把页面传回来告诉用户报错。这一来一回,可能要几十秒甚至一分钟。用户直接炸了
网景觉得,必须有一种脚本语言,能直接在浏览器里检查表单,不需要经过服务器
这个任务交给了 Brendan Eich
- BOSS的需求:一定要像 Java(Java 当时如日中天,网景想蹭热度
- Brendan 的私心:他其实想把 Scheme 或者 Self 搬进浏览器。他喜欢函数式编程
- 结果:为了赶在 Netscape Navigator 2.0 发布前上线,他只用了 10 天
这门语言最初叫 Mocha,后来改名 LiveScript,最后在发布前夕,为了营销,改名为 JavaScript
这就是后来无数人吐槽 JS 的根源:
- 它名字里有 Java,但跟 Java 没有任何关系
- 它的语法像 Java,骨子里却是函数式语言
- 它的设计充满了仓促的妥协:
null和undefined同时存在,甚至typeof null === 'object'这种著名的 Bug 被保留至今- 自动分号插入带来的莫名其妙的报错
- 极其宽松的类型转换(
[] + [] = "")
它被设计为一个玩具语言,仅仅用来做一些鼠标滑过图片变色、弹出 alert 对话框、检查输入框不为空的小把戏。
当时的专业程序员 (比如写Java的) 对 JavaScript 是嗤之以鼻的。他们认为这不是编程,这是脚本片段
但,潘多拉盒子已经打开了
哪怕它再简陋,它赋予了浏览器逻辑计算的能力 有了逻辑,网页就不再只是文档**,它开始向app进化
那时的前端,就在这一堆乱七八糟的表格标签、透明图片占位符、以及几行简陋的脚本验证代码中,跌跌撞撞地开启了 Web 的商业化时代
而就在不远的前方,一个名叫 Microsoft 的巨头马上就要睡醒了
2. 第一次浏览器大战
2.1 巨头的坠机
1996 年,Netscape Navigator 占据了近 80% 的市场份额。那时的浏览器是收费软件(或者说是共享软件),它是通往互联网的唯一大门
但比尔·盖茨醒了。他发出了那封著名的《互联网浪潮》备忘录,微软这艘航母开始全速掉头
微软采用了经典的Embrace, Extend, Extinguish战略:
- 拥抱:微软买下了 Spyglass Mosaic 的授权,改造出了 **Internet Explorer **
- 扩展:为了差异化竞争,双方开始疯狂地往 HTML 里塞私货
- Netscape 发明了
<layer>标签和<blink>(闪烁文本,我认为是史上最烦人的标签) - 微软发明了
<iframe >和<marquee>(滚动字幕) - Netscape 用
document.layers操作元素,微软用document.all - 结果开发者必须写大量的
if (document.all) { ... } else { ... }。这就是浏览器嗅探代码的鼻祖
- Netscape 发明了
- 毁灭:微软祭出了核武器:免费捆绑。IE 被预装在 Windows 95/98 里。网景唯一的收入来源断了
1998 年,网景惨败,被 AOL 收购。但在临死前,他们做出了一个改变历史的决定:开源(噔 噔 咚
他们把 Netscape 混乱的代码全部扔给了社区,成立了 Mozilla。这颗种子在地下埋藏了许多年,终长成名为 Firefox 的大树
2.2 IE6:帝国的荣光与坠机
2001 年 8 月,Windows XP 发布,随之而来的是 Internet Explorer 6.0
我们需要客观地评价这位昔日的王者。在 2026 年回看 IE6,它是落后、各种 Bug、不安全的代名词。但在 2001 年,IE6 是当时世界上最先进的浏览器,没有之一(毕竟不能拿现在的眼光审视过去
- 它是最好的 CSS 渲染器:相比之前的版本,它对 CSS1 的支持已经算不错了(虽然有著名的盒模型 Bug)
- 它是最快的 JS 引擎:那时候的 V8 还在娘胎里
- 它带来了 AJAX 的火种:
ActiveXObject("Microsoft.XMLHTTP")正是随 IE5/6 引入的
IE6 迅速占据了 95% 的市场份额。微软赢了,于是他们解散了浏览器团队(笑死 Web 的时间停止了
从 2001 年到 2006 年,整整五年,浏览器技术没有任何实质性的进步。这五年我称为**“Web 的黑暗中世纪”**
对于前端(这时候开始有专职的前端了),这五年是与 Bug 搏斗的五年:
- 盒模型 Bug:标准说
width是内容宽度,IE6 说width是包含边框的宽度。于是我们学会了 CSS Hack:_width: 200px;(只有 IE6 认识下划线开头的属性) - PNG 透明问题:IE6 不支持 PNG-24 的半透明,看到的都是灰色背景。我们被迫写滤镜代码 (
AlphaImageLoader) 或者继续用 GIF - 双倍边距 Bug:浮动元素的 margin 会莫名其妙加倍。解决办法?
display: inline
我们不再是构建者,而是修东西的。前端开发的门槛变成了一门玄学,你必须背诵几十个 IE6 的怪癖才能写出一个正常的页面
2.3 DHTML 与 Flash
在 HTML/CSS 停滞不前、标准分裂的年代,人类对动态交互的渴望并没有消失。这种渴望在两个方向上找到了出口
2.3.1 DHTML
DHTML (Dynamic HTML) 不是一个标准,而是一个营销词汇
它指的是:利用 JS 修改 CSS 属性,让静态的 HTML 动起来
那是特效的年代
- 网页上会有跟随鼠标飘动的文字轨迹
- 导航菜单展开时会有弹跳效果
- 状态栏会有跑马灯文字
大部分前端并不会写 JS,他们是脚本搬运工。他们去 DynamicDrive.com 这样的网站,复制一段几百行的 JS 代码,粘贴到网页里,然后改改参数。JS 在当时被视为一种装饰品,一种让网页看起来很酷(或者很杀马特)的玩具
2.3.2 Flash
既然 HTML 这么烂,不同浏览器渲染还不一致,那为什么不跳出浏览器呢?
Macromedia Flash(后被 Adobe 收购)横空出世
它实际上是一个浏览器里的“虚拟机”
- 一致性:不管你是 IE 还是 Netscape,只要装了 Flash 插件,效果完全一样。这是对兼容性地狱最有力的反击
- 多媒体:它能播放视频、音频(当时 HTML 做不到)
- 矢量绘图:它能做无损缩放的动画(当时没 SVG 的事情)
- 强大的语言:ActionScript。当 JS 还在处理表单验证时,ActionScript 2.0/3.0 已经是完全面向对象的语言了,有类、继承、接口
在那个年代,最顶级的前端工程师其实是 Flash Developer。他们做出了开心农场,做出了视频网站(土豆/优酷的早期播放器),做出了极其华丽的企业官网
Flash 是 Web 2.0 真正的前奏。
它证明了网页可以不仅仅是文档,而可以是游戏、电影和复杂的应用
但它是一个封闭的帝国,一个黑盒子。搜索引擎看不懂它,移动设备(后来的 iPhone)跑不动它
就在这看似无解的僵局中,Web 正在酝酿一场真正的革命
而在遥远的CA,Google 正在悄悄重写 Gmail 的代码,准备用 IE6 那个被遗忘的 XMLHTTP 接口,给世界一点小小的震撼
3. 网页开始呼吸
3.1 Gmail 的震撼
2004 年愚人节,Google 发布了 Gmail。当时所有人以为这是个笑话,因为它提供了 1GB 的免费存储空间(当时主流邮箱只有 2MB)
但真正让极客们震惊的不是容量,而是体验
在此之前,网页交互的模式是*同步的:
- 用户点击下一页
- 浏览器白屏
- 服务器生成新的 HTML
- 浏览器重新渲染整个页面
而在 Gmail 里,当你点击邮件标题时,网页没有刷新。列表保留在左边,邮件内容瞬间出现在右边。
这种体验太像原生app了,以至于很多人怀疑 Google 用了 Flash 或者 Java Applet
并没有 他们用的是纯正的、原生的、被微软遗忘在角落里的技术 XMLHTTP
- 这项技术是微软在 1999 年为了 Outlook Web Access 开发的,随 IE5 发布。但微软自己没重视它,把它当成一个不起眼的 ActiveX 控件
- Jesse James Garrett 的命名:2005 年,这位 UX 设计师写了一篇著名的文章,将这种“无需刷新页面就能与服务器通信”的技术统称为 AJAX (Asynchronous JavaScript and XML)
从此,网页有了后台线程 它可以在你不注意的时候,悄悄去服务器拿数据。网页开始呼吸了
3.2 JSON 一种偶然的标准
AJAX 中的 “X” 代表 XML。在当时,数据交换的标准格式确实是 XML 但是,在 JavaScript 里解析 XML 简直是灾难。你得写一堆 getElementsByTagName,代码冗长且慢
这时候,一位名叫 Douglas Crockford 的雅虎架构师站了出来 他发现 JavaScript 有一种原生的数据表示法——对象字面量
// XML<user><name>Chongxi</name><age>23</age></user>
// JSON{ "name": "Chongxi", "age": 23 }Crockford 甚至没有发明它,他只是发现了它。他把这种格式命名为 JSON
- 它天生就是 JS 对象,解析极其简单(早期直接用
eval(),虽然不安全) - 它比 XML 轻量得多(少了那么多尖括号,节省带宽)
这是 Web 历史上最四两拨千斤的胜利。 由于前端开发者的极力推崇,后端工程师发现生成 JSON 也比生成 XML 容易。于是,AJAX 里的 “X” 名存实亡,JSON 统治了世界
3.3 Firebug
在 2006 年之前,调试 JS 的唯一工具是: alert('Here 1'); alert('Variable x is: ' + x);
开发者像瞎子一样摸索。如果代码出错了,IE 只会左下角报一个黄色感叹号,告诉你缺少对象,行号永远是不准的
2006 年 1 月,Joe Hewitt 发布了 Firefox 的插件 Firebug
- 它能审查元素:鼠标指哪,高亮哪。上帝啊,我们可以直接在浏览器里改 CSS 看效果了
- 它有控制台 (Console):
console.log()终于取代了alert() - 它能断点调试
- 它能抓包
Firebug 是前端工程化的第一块基石。 它让前端开发从玄学瞎猜变成了科学观测
4. Write Less, Do More
4.1 救世主
虽然有了 AJAX,有了 Firebug,但前端开发依然极其痛苦 痛点在于:碎片化
- 要获取一个元素:
- 标准浏览器:
document.getElementById - 如果要按 Class 获取:IE6 不支持
getElementsByClassName。你得自己写循环去遍历 DOM
- 标准浏览器:
- 要绑定事件:
- 标准:
addEventListener - IE:
attachEvent
- 标准:
- 要获取计算后的样式:
- 标准:
getComputedStyle - IE:
currentStyle
- 标准:
每个前端工程师的电脑里都有一个 utils.js,里面存着几百行用来处理兼容性的脏代码
2006 年 8 月,John Resig 在 BarCamp NYC 上发布了 jQuery 它的核心理念极其简单粗暴:把所有浏览器差异藏在 $ 符号后面
4.2 链式调用的艺术
jQuery 发明了一种极具表现力的 API 风格——链式调用 (Chaining)
以前的代码:
var div = document.getElementById('CEPATO');div.style.color = 'red';div.style.display = 'block';div.onclick = function() { alert('clicked'); };jQuery 的代码:
$('#CEPATO').css('color', 'red').show().click(function() { alert('clicked');});- Sizzle 引擎:这是 jQuery 的核心。它让 JS 可以像 CSS 一样选择元素(
div > ul.list li:first-child)。这在当时是黑科技 - 隐式迭代:
$('.item').hide()会自动隐藏页面上所有的.item,不需要写for循环
jQuery 迅速成为了事实标准。甚至微软后来都在 Visual Studio 里内置了 jQuery 的智能提示 那时候的前端面试题:jQuery 源码读过吗?Sizzle 引擎是怎么实现的?
4.3 copycat 黄金时代
jQuery 的另一个伟大之处在于它的插件机制 ($.fn.extend) 这开启了前端最早的组件化雏形
- Lightbox:点击图片,背景变暗,图片放大
- Carousel:轮播图
- Datepicker:日期选择器
无数不懂 JS 原理的切图仔,靠着下载 jQuery 插件 + 改 CSS,就能拼凑出一个交互丰富的网站
jQuery 极大地降低了前端门槛,但也埋下了祸根: 大量的面条代码和滥用的 DOM 操作,导致页面性能越来越差
5. 引擎的军备竞赛
5.1 Speed
随着 AJAX 和 jQuery 的普及,网页里的 JS 代码量指数级增长
从几百行,变成了几千行,甚至几万行…然后
浏览器跑不动了
当时的 JS 引擎都是解释执行的:读一行,跑一行。效率极低 Web 应用想取代桌面软件,最大的瓶颈就是慢
5.2 Google 的阳谋
2008 年 9 月,Google 发布了一本漫画书,介绍了一款新浏览器:Chrome 与之同来的,是一个全新的 JS 引擎:V8
V8 做了一件惊天动地的事:JIT 它不解释代码,它是把 JavaScript 直接编译成机器码运行
- V8 刚发布时,JS 执行速度比 IE 快了不知道多少倍
- 多进程:一个标签页崩了,不会导致整个浏览器崩溃。这也是 Chrome 的首创
V8 的出现改变了一切
- 它证明了 JS 可以跑得飞快
- 它让 JS 有能力处理极其复杂的逻辑(不仅仅是表单验证)
- 既然 V8 这么快,而且它是独立的 C++ 库,那为什么不能把它拿出来,在服务器上跑 JS 呢?(而一年后,Node.js 诞生了)
5.3 WebKit
与此同时,Apple 开源的 WebKit 内核(源自 KHTML)开始崛起。Safari 和早期的 Chrome 都使用了 WebKit 它是移动互联网的基石。第一代 iPhone 发布时,乔布斯不支持 Flash,只支持 Web 标准 这意味着,即将到来的移动 Web 浪潮,将是 WebKit 的天下…吗?
至此,前端已经拥有了:
- 交互能力
- 数据标准
- 开发工具
- 标准库
- 高性能引擎
万事俱备
但随着代码量的膨胀,我们即将迎来一场巨大的危机
我们有了枪炮,但还缺乏战术。我们写得出炫酷的特效,但维护不了庞大的系统
接下来咱就要讲工程化与架构革命 Node.js 登场,前端终于要开始像正经的软件工程师那样思考了
6. Nodejs
如果说前二十年,前端还是在浏览器这个沙盒里带着镣铐跳舞,那么从 2010 年开始,前端工程师通过一次惊天动地泣鬼神的越狱,彻底改变了游戏规则
6.1 Ryan Dahl 的疯狂实验
2009 年的 JSConf EU 大会上,一个名叫 Ryan Dahl 的年轻人做了一场演示
他做了一件看似违背祖宗的事情:他把 Google Chrome 里的 V8 引擎抠了出来,给它装上了文件系统和网络模块,让它跑在了服务器上
这就好比把法拉利的引擎拆下来,装在了一台拖拉机上
这个项目叫 Node.js
起初,大家以为它是后端技术的革命(非阻塞 I/O,高并发)
但很快,前端工程师们发现了一个惊天秘密:补兑,既然服务器能跑 JS,那是不是意味着 我们可以用 JS 来写…
在此之前,如果你想压缩一个 JS 文件,你得用 Java 写的 YUI Compressor
如果你想预处理 CSS,你得用 Ruby 写的 Sass
前端开发者的工具链掌握在别人手里
Node.js 的出现,让前端拥有了造轮子的权利
6.2 npm
2010 年,Isaac Z. Schlueter 发布了 npm
在这之前,如果我们想用 jQuery,得去官网下载 jquery.js,放到项目文件夹里,然后在 HTML 里写 <script src="jquery.js">。如果 jQuery 依赖了别的库,你根本不知道,直到报错
npm 改变了一切: npm install jquery
这不仅是一个下载工具,它引入了语义化版本控制 和 依赖树 前端代码不再是散落在各处的碎片,而是变成了一个有组织、可复用的模块体系
6.3 自动化构建:Grunt 与 Gulp
既然有了 Node.js (运行时) 和 npm (包管理),前端工程师开始编写自动化脚本
- Grunt:基于配置的构建工具。你需要写一个巨大的 JSON 对象,告诉它先把 A 文件和 B 文件合并,然后压缩,然后重命名
- Gulp:基于流的构建工具。代码写起来像管道一样流畅:src('src/*.js').pipe(babel()).pipe(uglify()).pipe(dest('dist/'));
这是前端工程化的开端 我们不再手动刷新浏览器,不再手动压缩图片 前端开发从手工进化到了流水线
7. MVC 混战与移动大迁徙
7.1 乔布斯的判决书与 HTML5 的上位
2010 年 4 月,Steve Jobs 发表了著名的公开信**《Thoughts on Flash》** 他列举了 Flash 的六大罪状(耗电、触摸屏支持差、封闭等),并宣布 iPhone/iPad 将永远不支持 Flash
这一刻,Web 的命运被改写了
移动互联网的大门轰然打开,而唯一的通行证是 HTML5
于是,所有的企业都需要开发移动版网页
但手机的 CPU 和网络比电脑差得多,屏幕也小得多
简单的 jQuery 特效在手机上卡顿无比。我们需要更高效、更像原生 App 的网页
7.2 意大利面条代码危机
随着需求越来越复杂(比如做一个网页版的 Excel 或 Trello),前端代码量突破了 10 万行
jQuery 的弊端彻底暴露:它把状态藏在了 DOM 里
想象一下,你要做一个购物车
- 用 jQuery:你需要从
span.total-price里读出文本,转换成数字,加 1,再写回span - 如果页面上还有三个地方要显示总价呢?你得手动更新三个地方
- 如果漏了一个怎么办?Bug 就来了
这种面多加水,水多加面的代码,错综复杂,被称为意大利面条代码
一旦项目变大,维护它简直是地狱
7.3 寻找秩序
为了解决这个问题,前端开始向后端偷师,引入了经典的软件架构模式:MVC
核心思想是:数据(Model)是源头,视图(View)只是数据的投影
7.3.1 第一代探索者:Backbone.js
Backbone 非常轻量。它给了你 Model、Collection、View 三个基类
- 它告诉你:把数据存在 Model 里,不要存在 DOM 里
- 当 Model 变了,Model 会触发一个
change事件 - View 监听到事件,手动去更新 DOM(还是用 jQuery)
- 它建立了秩序,但写起来还是很累,很多胶水
7.3.2 AngularJS (v1
2009 年发布,2012 年爆火。AngularJS 带来了两个震撼世界的概念:
双向数据绑定:
<input ng-model="username"><h1>Hello, {{ username }}</h1>你在输入框里打字,下面的
<h1>里的文字实时跟着变 不需要写任何 JS 事件监听代码 这在当时简直是魔法依赖注入: 你想要用
$http服务?只需要在函数参数里写上它,Angular 就自动给你送进来
AngularJS 让 HTML 变成了一种模版语言。它让开发效率提升了 10 倍
一时间,全世界都在用 Angular 重写项目。前端工程师觉得自己终于是在开发软件,而不是在写脚本了
7.4 AMD vs CMD
在 Node.js 和框架爆发的同时,还有一个基础问题没解决:JavaScript 语言本身没有模块系统(直到 ES6)
我怎么在一个 JS 文件里引用另一个 JS 文件? <script> 标签不仅丑,而且依赖顺序很难管理(jquery.js 必须在 plugin.js 之前)
于是,民间标准诞生了:
- CommonJS:Node.js 用的标准 (
require/module.exports)。是同步加载的,适合服务器,不适合浏览器(网络请求要等) - AMD (RequireJS):异步模块定义。
define(['dep1'], function(dep1) { ... })。它是浏览器端的霸主 - CMD (SeaJS):淘宝玉伯提出的标准,更符合 CommonJS 的书写习惯(就近依赖)
这不仅是技术之争,也是社区话语权之争
至此,我们完成了工业化的初步积累
- 我们有了构建工具,开始像正规军一样开发
- 我们有了MVC 框架,开始分离数据和视图
- 我们有了移动端市场,地位空前提升
但是,AngularJS 的双向绑定在大规模应用中出现了严重的性能问题
而 FaceBook 的工程师们,正在为一个叫做 Instagram 的应用头疼。他们觉得 MVC 模式在前端也是错的
他们提出了一个更激进的想法:如果视图只是一个函数呢?
8. React 与 范式转移
8.1 JSX 的离经叛道
2013 年,当 Facebook 的工程师 Jordan Walke 开源 React 时,整个社区的反应是愤怒的
甚至有人说:这是把我们过去十年的努力全毁了
Why? 因为 JSX 在过去,最佳实践是关注点分离:HTML 归 HTML(模版),JS 归 JS(逻辑)
React 却说:不,我们要把 HTML 写在 JS 里
React 的核心洞察是:UI 渲染逻辑(事件处理、状态变化)和 UI 展示逻辑(DOM 结构)本质上是紧密耦合的。硬把它们分在不同文件里,只是物理分离,而不是逻辑分离
JSX 不是模版,它是 JavaScript 的语法糖。这意味着你拥有一门图灵完备语言的所有能力(变量、if/else、循环、函数)来描述界面,而不是只能用蹩脚的模版语法(ng-repeat, v-if)
这是一场思维革命:我们不再编写页面,我们在编写组件
8.2 Virtual DOM
React 被骂得最惨的时候,是它的性能拯救了它。 当时 Facebook 遇到了一个难题:在极其复杂的 Feed 流里,如何高效更新消息?
如果是 jQuery,你要手动找哪个 DOM 变了;如果是 Angular 1,它会疯狂地进行脏检查(Dirty Checking),导致页面卡顿
React 提出了 Virtual DOM:
- 快照:每次状态变了,我都在内存里生成一棵新的虚拟 DOM 树(纯 JS 对象)
- 对比:我用高效的算法(O(n))对比新旧两棵树,找出哪怕一个属性的变化
- 修补:我只把变化的这一点点应用到真实的浏览器 DOM 上
这是数学的胜利
React 甚至不需要知道浏览器的存在。它把渲染抽象成了一个函数:UI = f(State) 只要状态变了,UI 就自动变。开发者再也不用碰 document.getElementById 了。手动操作 DOM 成为历史
8.3 Redux 的苦行僧
React 只解决了 View,那数据怎么办?
Facebook 提出了 Flux 架构,后来Dan Abramov 把它简化为 Redux
Redux 是极其繁琐的:Action, Reducer, Store, Dispatch。改一个字段要写四个文件
为什么我们要这么折磨自己?
因为在大型应用里,可预测性比便捷性更重要
- 单向数据流:数据只能从上往下流
- 纯函数:Reducer 必须是纯函数,同样的输入永远得到同样的输出
- 倒带:因为状态是不可变的,你可以随时回退到之前的状态
Redux 治好了 MVC 时代的状态管理混乱症,虽然代价是大量的样板代码
9. Vue 的渐进式哲学
9.1 尤雨溪的减法
就在 React 试图用函数式编程教育世界的时候,一个 Google 的前员工尤雨溪在想:“有没有一种办法,既能有 Angular 的模版便利性,又有 React 的虚拟 DOM 性能?”
2014-2015 年,Vue.js 开始爆发
它的核心哲学是渐进式
- 你想用 CDN 引入写个小挂件?没问题,像 jQuery 一样用
- 你想写个单页应用?没问题,上 Vue Router 和 Vuex
- 你想搞大型工程?没问题,上 Vue CLI
Vue 没有强迫你学会 JSX,它保留了 HTML 模版。这让无数从 HTML/CSS/jQuery 时代过来的开发者感到了温暖。在大中华区,Vue 成为了绝对的统治
叠甲:用「大中华区」表述单纯是因为个人习惯,不喜勿喷
9.2 响应式魔法 Object.defineProperty
Vue 2.0 最迷人的地方在于它的响应式系统 它利用了 ES5 的 Object.defineProperty(Getters/Setters)
- 当你把一个对象传给 Vue 的
data时,Vue 会遍历它的所有属性,把它们变成 Getter/Setter - 当组件渲染时,读了哪个属性,Vue 就记下来这个组件依赖这个属性
- 当属性变了(Setter 被调用),Vue 通知所有依赖它的组件更新
这比 Angular 1 的脏检查高效,比 React 的手动 setState 自动化。它是魔法,但对于大多数开发者来说,这是一种幸福的魔法
10. 构建的巴别塔
10.1 Webpack 炼金术
在这个时期,前端工程师最痛恨的文件大概是 webpack.config.js 随着 SPA的普及,一个项目可能有几百个 JS 文件、几十个 CSS 文件、几百张图片
Webpack 站出来说:在我眼里,Everything is a module
- JS 是模块,CSS 也是模块(
import './style.css'),图片也是模块(import img from './logo.png') - Loader:它是一个转换器,把 Sass 转成 CSS,把 ES6 转成 ES5,把图片转成 Base64
- Plugin:它是一个大管家,负责压缩代码、分割代码(Code Splitting)、注入环境变量
Webpack 极其强大,也极其复杂。它甚至催生了一个新工种 Webpack 配置工程师 但也正是 Webpack,让前端终于有了工业级的构建流水线。我们终于可以做 Tree Shaking(摇树优化,用于去掉没用的代码),做按需加载
10.2 让我们活在未来
2015 年 6 月,ES6 (ECMAScript 2015) 正式发布
这是 JavaScript 诞生以来最大的更新:class, module, arrow function, promise, const/let
JS 终于像一门正经语言了
但是,浏览器(尤其是万恶的 IE)不支持啊
Babel(原名 6to5)出现了
它是一个编译器。它把 ES6 代码读进去,转换成等价的 ES5 代码吐出来
这意味着:前端开发者不再受限于用户的浏览器版本
我们可以使用 2026 年的语法标准写代码,通过 Babel 跑在 2010 年的浏览器上
这是前端自信心的极大提升:我们掌控了语言的发展权
10.3 TypeScript 的浪子回头
到了 2018 年左右,随着项目规模突破百万行代码,弱类型的 JS 成了最大的隐患
“Cannot read property ‘x’ of undefined” 是所有线上事故的罪魁祸首
Microsoft 的 TypeScript 终于熬出头了
- 它是 JS 的超集
- 它带来了静态类型检查
- 它带来了极其强大的 IDE 智能提示
起初,大家嫌弃写类型麻烦(把Ts写成anyScript 但当你在重构一个 5 年前的老组件,IDE 精准地告诉你哪里报错时,你会跪下来感谢 TypeScript
无 Ts,不前端逐渐成为了行业共识
至此,现代前端的四大支柱确立了:
- 组件化框架
- 类型系统
- 构建工具
- 现代语法
我们建立了一座宏伟的巴别塔 但是,这座塔越来越重了 node_modules 文件夹变成了黑洞(没那么轻)。一次 npm install 要 5 分钟,一次 npm run build 要 3 分钟(没那么快) SPA 的首屏加载越来越慢,SEO 依然是痛点
历史的钟摆,即将再次摆动
为了追求极致的速度,我们将开始反思:我们是不是把太多东西塞进浏览器了?
11. 静态复兴
11.1 SPA 的代价
在 2016-2019 年,如果你不做 SPA,你都不好意思说自己是前端
但是,当全世界都用 React 写网页时,问题暴露了:
- TTFP:用户打开网页,必须先下载几兆的 JS bundle,浏览器解析完,才能渲染出第一行字。在 3G 网络下,这意味着 5-10 秒的白屏
- SEO 归零:Google 的爬虫虽然能执行 JS,但Bing或者百度的爬虫经常抓瞎。对于新闻、电商网站来说,搜不到等于死
CSR的神话破灭了
11.2 Next.js 与 Nuxt
Vercel(Next.js 背后的公司)站了出来
他们的思路很简单:既然浏览器渲染慢,那我们回服务器渲染不就行了?
- 同构渲染:
- 用户第一次访问:服务器跑 React 代码,生成 HTML 字符串,直接返回。速度极快,爬虫友好
- 用户后续点击:浏览器接管,变成 SPA。体验丝滑
这听起来很像 1998 年的 PHP/JSP,但区别在于:前后端是一套代码(全是 JS/TS)。前端工程师拿回了渲染的主导权,同时兼顾了性能
11.3 静态生成
对于博客、文档、营销页,内容几百年不换一次,为什么要每次请求都算一遍?
Gatsby 和 Next.js 推广了 SSG
- 构建时:在开发者电脑上,React 把 1000 篇博客编译成 1000 个
.html - 分发:扔到 CDN 上
- 访问:用户访问时,就是从离他最近的 CDN 节点拿一个静态文件。这是物理定律允许的最快速度
我们绕了一圈,回到了 Web 1.0 的静态文件模式,但生产这些文件的,是 Web 4.0 的自动化流水线
12. 水合与欺骗的艺术
12.1 恐怖谷效应
SSR 和 SSG 带来了一个新问题:“注水
当服务器返回 HTML 时,网页看起来画好了,按钮也在那儿
用户急不可耐地去点按钮:没反应
为什么?因为 JS 还没下载完,事件监听器还没挂载上去
这段时间被称为恐怖谷。用户以为它是活的,其实它是死的
这是现代前端最大的痛点:为了让用户早点看到内容,我们不仅发了 HTML,还得再发一份一模一样的 JS 去激活它。数据发了两遍
12.2 岛屿架构
2021 年,Astro 提出了一个灵魂拷问: 我的blog文章是静态的,为什么我要加载 React 库来渲染它?
Astro 的方案是 0 JS
- 整个页面是纯 HTML
- 只有那个评论区组件和点赞按钮是 React 组件
- 浏览器只加载那两个小岛的 JS
这是对 React 全家桶的反叛。它标志着前端开始追求细粒度的加载。我们不再把一头大象塞进冰箱,我们只放进去几根香蕉
这也是我个人是非常喜欢Astro的原因之一
12.3 边缘计算
现在的后端,不再仅仅只是机房里的一台服务器,而是遍布全球的 Edge Functions
Vercel / Cloudflare Workers 允许前端工程师写一段 JS 代码,部署到全球 200 个城市
用户的请求会被路由到离他最近的节点,那里有一个微型的 V8 环境在等他
前端工程师的边界,已经拓展到了网络的最边缘
13. 工具链的降维打击
13.1 Vite 闪击前端
2020 年,尤雨溪(又是他)发布了 Vite 当时 Webpack 的痛点是:启动一个大项目开发服务器,需要等 30 秒甚至 1 分钟。因为 Webpack 要把所有文件打包一遍
Vite:现在的浏览器已经支持 import 了,为什么还要打包?
- No-Bundle 开发:Vite 启动时几乎不做事。你请求哪个文件,它就编译哪个文件。启动时间:300ms
- 这种体验上的巨大差异,让 Vite 像病毒一样迅速吞噬了 Webpack 的市场份额
13.2 Rust 与 Go 的入侵
Vite 的底层使用了 esbuild(用 Go 写的) Next.js 的底层使用了 SWC / Turbopack(用 Rust 写的)
前端工具链正在经历一场**“降维打击”**。
- 以前:用 JS 写 JS 的编译器(Babel, Terser)。慢,单线程
- 现在:用系统级语言(Rust/Go)写 JS 的编译器。快,多核并行
- 结果构建速度提升了 10 倍到 100 倍
这标志着前端基建的门槛被极大地拔高了。以前你会写 JS 就能造轮子,现在你得懂 Rust 和内存管理
13.3 CSS 的原子化:Tailwind CSS
在样式领域,一场审美审丑的战争结束了 Tailwind CSS 赢了
它看起来很像 1999 年的内联样式:<div class="flex items-center justify-between p-4 bg-blue-500"> 很多人第一次看觉得丑爆了 但用久了就感觉爆赞了:
- 不用想类名:再也不用纠结
container-inner-wrapper这种名字了 - 没有死代码:CSS 文件不再无限膨胀
- 设计系统:颜色、间距都是标准化的
它改变了前端写样式的肌肉记忆
14. 前端已死,前端万岁
14.1 全栈的回归
React 最近推出了 RSC 你可以在 React 组件里直接写 SQL 查询数据库:
// This runs on Serverasync function UserList() { const users = await db.query('SELECT * FROM users'); return <div>{users.map(u => <p>{u.name}</p>)}</div>;}前后端的界限彻底模糊了(但这也导致了更逆天的问题出现
前端工程师不再是切图仔,也不再是API 消费者
我们正在变成产品工程师。我们一个人就能构建一个完整的、高性能的应用
14.2 AI
2023 年,ChatGPT 等各家LLM来了 写一个居中布局?写一个正则验证?写一个复杂的 Reducer?
AI 一秒钟就生成
前端开发的门槛到底是变低了还是变高了?
- 低级代码 的门槛消失了。AI 会比你写得快,被AI取代的也是这些
- 系统架构 的门槛变高了。你需要懂 SSR、Hydration、Database、Security等乱七八糟的东西,才能指挥 AI 把它拼成一个好的产品
14.3 下一个三十年
回顾这三十年: 从 1990 年的 <table> 布局,到 2025 年的 Rust 编译器驱动的 Edge SSR
- 我们曾为了动,把一切搬到浏览器
- 我们现在为了快,把一切搬回服务器
历史不是简单的圆圈,而是螺旋上升的塔
我们回到了服务器,但我们带回了组件化、声明式编程和极致的工程化体系
Web 依然年轻 只要人类还需要通过屏幕与数字世界交互,前端工程师的使命就永远不会结束
15. 结
至此,我们过了一遍属于前端的三国演义
我们从从蒂姆·伯纳斯-李的草稿纸开始,一直讲到现在 这就是无数开发者与浏览器搏斗、妥协、并最终驯服它的过程
为什么要学这么多乱七八糟的工具?
因为我们是在一堆原本用来擦屁股的纸上,通过几十年的叠Buff,硬生生盖出了一座摩天大楼

