一、为什么要优化 CSS 与 JS
CSS 和 JavaScript 是阻塞渲染的元凶。浏览器解析 HTML 时遇到 link 引入的 CSS 会停下来等待下载和解析,遇到 script 也会阻塞后续渲染。文件越大、请求越多,首屏渲染就越慢,直接拖累用户体验和搜索引擎的核心网页指标。移动端网络环境下这个问题更明显,一个 500KB 的 JS 文件在弱网下可能要好几秒才能下载完。
优化的三个方向:压缩减小体积、合并减少请求、调整加载时机避免阻塞。三者结合通常能把前端资源体积缩小一半以上。优化前建议先用开发者工具的网络面板看一眼,找出体积最大、耗时最长的几个资源,优先处理,不要平均用力。
二、压缩:把文件体积降下来
压缩分两层:第一层是构建时压缩源代码,去掉注释、空格和多余符号,同时把变量名缩短。CSS 用 cssnano 或 clean-css,JavaScript 用 terser 或 UglifyJS,webpack 和 Vite 都内置了这些能力,配置生产模式构建即自动压缩。压缩后的代码可读性差,但体积通常能减小三到五成。
第二层是传输时压缩,Nginx 开启 gzip 或 brotli,对 text/css、application/javascript 等类型压缩后再发送,通常能再省六到八成的传输体积。gzip 的配置很简单,一个典型的配置块如下:
gzip on; gzip_types text/plain text/css application/javascript application/json image/svg+xml; gzip_min_length 1024; gzip_comp_level 6;
注意 gzip 对已经压缩过的图片无效,只对文本类资源生效。压缩后的文件要确认没有语法错误,可以用线上工具或本地脚本验证,也可以发布后打开页面看控制台有没有报错。构建产物和线上版本要一一对应,避免开发环境正常、线上压缩后出问题的尴尬情况。与压缩配套的还有移除无用代码:Tree Shaking 会在构建时剔除只导入未使用的模块和函数,配合代码检查工具定期清理废弃依赖,能让打包体积持续保持精简。
三、合并:请求数量的取舍
HTTP/1.1 时代浏览器对同一域名并发连接有限,合并文件能显著减少请求数。但 HTTP/2 普及后,多路复用让多个小请求的开销大幅下降,盲目合并反而可能损害缓存命中率——改一行代码就要重新下载合并后的大文件。合并之前先确认服务器是否开启了 HTTP/2,没开启的话合并的价值还是很大的。
现在的推荐做法是适度合并:公共库合并成一个文件,业务代码按模块拆分成合理数量。构建工具会自动处理依赖关系,把第三方库与业务代码分离,充分利用浏览器缓存。第三方库基本不变,缓存命中率很高;业务代码频繁更新,独立成文件可以只更新自己那部分。
四、关键 CSS 与加载时机
首屏渲染只需要少量 CSS,把首屏需要的样式内联到 HTML 的 style 标签里,其余样式用 media 属性或延迟加载,可以显著提升首次内容绘制。这个技术叫关键 CSS,webpack 的插件可以自动提取首屏样式。内联的样式要控制体积,一般几 KB 以内,太大反而拖慢 HTML 本身的下载。
JavaScript 的加载时机用 defer 和 async 控制:defer 让脚本在文档解析完成后执行且保持顺序,async 让脚本下载完立即执行不保证顺序。页面初始化不依赖的脚本(统计代码、广告脚本、轮播插件)尽量用 defer 或 async,避免阻塞解析。把脚本标签放在 body 结束标签前是传统做法,配合 defer 效果更好。加载方式的选择取决于脚本是否依赖 DOM 结构和其他脚本,依赖关系的脚本用 defer 保持顺序,完全独立的用 async。
五、按需加载与代码分割
单页应用和大型站点的 JavaScript 动辄几 MB,全部首屏加载显然不合理。代码分割把代码按路由或组件拆成小块,访问哪个页面只加载对应的代码块。Vite 和 webpack 都支持动态 import 语法自动分割,例如把编辑器、图表库这类重量级组件单独拆包,用户用到时才加载。首屏只保留核心逻辑,能把初始包体积砍掉一大半。
图片懒加载也是按需加载的重要部分,loading="lazy" 属性一行代码即可实现,视口外的图片滚动到附近才开始加载,节省大量流量和首屏时间。视频和 iframe 也可以用类似思路,先加载占位图,用户点击或滚动到附近再真正加载。懒加载要注意给图片预留尺寸,避免加载时页面布局跳动,影响布局稳定性指标。
六、缓存与指纹
前端资源要充分利用浏览器缓存,给静态资源设置长缓存时间,比如一年,同时用内容哈希做文件名指纹:文件内容变了哈希就变,URL 跟着变,浏览器自然请求新文件;内容没变则直接命中缓存。webpack 的输出文件名配置 contenthash 即可,Nginx 配合 expires 指令设置缓存头,例如 location 匹配静态文件目录时设置 expires 1y。
指纹和长缓存配合是静态资源提速的黄金组合,比单纯设置缓存头效果好得多,因为文件名变化能精确控制缓存失效时机。部署时注意顺序:先上传带新指纹的文件,再更新 HTML 引用,避免中间状态出现 404。HTML 本身不要设置长缓存,或者用协商缓存,保证页面内容及时更新。
七、字体与第三方脚本优化
自定义字体是经常被忽视的性能杀手。字体文件动辄几百 KB,加载慢还会导致文字先隐藏再出现。优化手段包括:只引入用到的字重和字符子集,用 font-display: swap 让文字先用系统字体占位,国内站点尽量使用国内 CDN 或自托管字体文件,避免跨境的字体请求拖慢页面。
第三方脚本(统计、广告、在线客服等)也要管理:能自托管的就自托管,减少对第三方域名的依赖;给外部脚本加上 integrity 属性做完整性校验,防止被篡改;用 dns-prefetch 和 preconnect 提前建立与第三方域名的连接,缩短后续请求的等待时间。第三方脚本数量控制在合理范围,每加一个都问问自己是否真的需要。
八、测量与持续优化
优化效果要用数据说话。Chrome DevTools 的 Lighthouse 能给出性能评分和优化建议,PageSpeed Insights 可以模拟真实网络环境测试移动端和桌面端。重点关注首次内容绘制、最大内容绘制、布局偏移三个核心指标。真实用户数据可以通过浏览器的性能面板或第三方监控工具采集,比实验室数据更能反映实际情况。
优化不是一次性的工作,代码在持续演进,建议每次发布前跑一遍 Lighthouse,把性能分数作为发布检查项之一。长期坚持,网站速度会稳定保持在一个好的水平。性能优化是一个持续迭代的过程,先建立测量基线,再针对得分最低的项改进,每轮解决一两个问题,效果会越来越明显。
总结
前端性能优化从压缩、合并、加载时机三件事入手,配合构建工具、缓存指纹和测量工具,就能把网站速度提升一个档次。优化空间永远存在,关键是先建立测量基线,再有针对性地改进,避免盲目优化。把本文的方法应用到自己的站点上,对比优化前后的 Lighthouse 分数,你会看到实实在在的变化。