
1. 翻开2007年的军规哪些原则已经过时哪些今天依然是硬标准1.1 军规的来龙去脉从拨号网络到千兆宽带一提前端性能优化老工程师多半会甩出雅虎35条军规这份清单。我刚带团队那会儿也逐条对过结果发现有些规则直接照搬不但不省心反而拖慢开发节奏。后来重新梳理每条规则背后的原理才摸清什么值得照做、什么要改一版、什么已经彻底过时。雅虎35条军规是十多年前总结出来的前端性能规范那时候HTTP/1.1一统天下一个域名只能维持少数并行连接浏览器加载十几个资源都费劲。所以清单第一条就是“减少HTTP请求数”几乎所有手段都围绕这个目标合并脚本、合并样式、雪碧图、内联小图片目的都是让浏览器少发几个请求。今天再翻开这份清单很多人觉得过时了。HTTP/2有了多路复用HTTP/3换了传输层带宽焦虑小了很多。但如果你真把这份老清单丢进垃圾桶多半会吃亏。它最大的价值不是那35条具体做法而是一套判断性能问题的底层思路先看网络层、再看渲染层、再看执行层最后回到用户体验本身。我接手过一个运营后台项目首页资源不到50KB请求数也压到了30个以内但用户还是反馈慢。单纯看清单似乎都达标了真正的问题出在请求发起顺序和渲染阻塞上。这个例子让我意识到读军规不能当说明书背要当诊断手册用。1.2 今天仍然不能动的四条铁律35条里有四条我至今视为硬标准改动任何一条都会在真实用户身上立刻看到收益或反弹。第一是合理配置缓存。Expires、Cache-Control、ETag这些头字段的正确设置直接决定用户二次访问的速度。很多项目只开了缓存头却忘了给静态资源加版本号结果线上更新后用户拿到旧文件。第二是启用压缩。文本类资源必须压缩传输这点从Gzip时代到Brotli时代从未变过。我只见过没压缩的站点没见过压缩后反而变慢的站点。第三是避免阻塞渲染。CSS要尽早给浏览器JS不要挡在关键渲染路径上。移动端尤其明显一个同步脚本就能让首屏白屏多一秒。第四是减少不必要的DOM访问。JS运行时的性能大头往往不在算法而在反复读写DOM。这四条分别对应网络、传输、渲染、执行四个层面正好构成一个完整排查框架。老清单里二十几条技巧基本都能归入这四个层面。1.3 已经变形的规则合并请求、域名拆分与25KB限制有些规则在今天的网络环境下要重新解释。比如减少HTTP请求数HTTP/2多路复用之后几十个小文件的传输效率没有想象中差但浏览器仍会为每个请求付出协议开销、解析开销和缓存碎片对慢速移动网络请求数依然是敏感指标。做法上不再是粗暴地“所有JS合并成一个文件”而是按路由拆分、按优先级加载。再比如把组件分散到多个域名当年是为了突破同域名并发连接数限制。现在做多域名只会增加DNS查询和TLS握手成本除非有非常可靠的CDN资源拆分理由否则我不建议照搬。还有一条“组件保持在25KB以内”那是当年TCP拥塞窗口和带宽的历史产物。现在这个数值意义不大但它提醒我们单文件体积要控制我一般会把不超过200KB作为新项目的硬线超过就警惕。规则会过时规则背后的思考方式不会。2. 砍网络开销请求数、缓存和压缩的现代策略2.1 减少HTTP请求在HTTP/2下还得算这笔账先说实测数据。同样一个页面在HTTP/1.1下从80个请求压到25个首屏时间能少一半在HTTP/2下从80个压到25个首屏时间可能只改善20%。如果站点已经切了HTTP/2你花一晚上把七八个脚本文件强行合并成一个大JS收益往往低于预期反而可能因为缓存粒度变粗更新时连带加载全量文件。所以现代策略不是“少”而是“精”。按关键路径拆分首屏必需的资源优先加载、提前预加载非关键的脚本延迟到交互发生后同一路线的资源可以打包。我自己的操作顺序是先移除无用请求再合并必要请求最后用preload和prefetch调整加载时机。工具上浏览器开发者工具的网络面板按大小和耗时排序几秒钟就能找出占了总流量一大半的那几个图片和字体。先把明显垃圾清掉比纠结合并算法重要得多。2.2 缓存不是“开着就行”头字段与版本URL配合很多项目碰到用户看到旧页面的问题第一反应是关掉缓存。我建议别干这事正确做法是让URL和内容版本绑定。CSS文件叫app.8f3c2a.css而不是app.css发布新版后浏览器把旧文件当垃圾自然去请求新地址旧缓存也不会污染。配合关系是这样的静态资源配置长缓存Cache-Control: max-age31536000, immutableHTML页面配置短缓存或协商缓存让用户每次能拿到最新入口。两者结合既保证回访快又保证更新及时。ETag建议保留它解决的是文件内容没变但服务器时间戳变了的场景避免无谓传输。还有一个容易忽略的坑按需加载的异步文件也要遵循同样的版本规则。不要因为它是动态import就放任自流否则用户在一个版本迭代后可能拿到404。2.3 压缩那个选项真的开了吗从Gzip到Brotli我排查过很多怎么优化都不快的站点最后发现服务器根本没开压缩。页面源文件30KB传输时还是30KB。打开Gzip之后变8KB效果立竿见影。现在性能更好的方案是Brotli压缩率普遍比Gzip高10%到20%尤其对HTML、CSS、JS这类文本资源。前提是服务器和CDN都支持且只在HTTPS上启用。注意别对已经压缩过的图片、视频再做文本压缩纯属浪费CPU且体积不变。文本资源的压缩级别也要讲分寸。最高压缩率会显著占用服务器CPU上线高峰可能拉高响应时间一般把文本压缩级别设为中等偏高就够了。无脑用最高级高并发时会吃亏。2.4 重定向与404一次跳转多一次全往返35条里有一条避免404很多人觉得是废话。实际项目里404多发生在老链接被移除、图片路径写错、或部署后静态资源没有同步。每个404在浏览器里就是一次无效请求用户侧看到空白或报错性能侧则浪费了连接和解析时间。重定向同理。一次302从a.com跳b.com至少多出一整个HTTP往返移动网络下可能就是几百毫秒。排查时在开发者工具里看网络面板中灰色或带箭头的请求凡是出现重定向链条的想办法让用户直接命中最终地址。特别是入口页面和分享链接重定向对首屏伤害最大。3. 让首屏早点画出来关键渲染路径里最容易踩的坑3.1 CSS放顶部、JS放底部原理是什么浏览器有个固定流程收HTML、建DOM、遇到CSS就建CSSOM、两者合成渲染树、再布局和绘制。CSS在底部到达浏览器只能等它处理完再渲染会白白浪费已下载的HTML。所以外部样式表放在head里不是排版偏好是缩短首屏等待的最基础手段。JS更麻烦。普通script标签一旦开始下载和执行HTML解析就要停下。放在底部能等DOM结构基本建好再执行脚本减少白屏时间。老军规那句脚本放底部到今天依然是默认做法只是现代工具链给了我们更好的位置选择。3.2 现代异步加载方案defer、async与动态import底部放脚本能解决一部分问题但需要精细控制时我用两个属性defer和async。defer让脚本在HTML解析完成后按顺序执行适合依赖DOM的普通业务脚本async适合独立性很强的第三方脚本下载完就执行不等待其他脚本。两者的核心区别是执行时机和顺序。简单记业务逻辑用defer广告统计之类用async。如果团队用了打包工具输出产物通常自带defer处理开发时不用手动加。更细的优化是动态import按路由加载组件用户点进某个功能时才拉取对应代码初始包体积能降一大截。3.3 内联与外链的取舍极端优化用什么姿势35条里有一条把CSS和JS放到外部文件理由是利于缓存这没错。但在首屏有限资源内外部文件意味着HTML解析完还要再等一个请求。权衡下来我见过一些站点会把首屏必需的少量CSS内联在HTML里配合不带缓存的策略首屏能快一到两次往返。不过内联要克制。把所有样式都内联进HTML等于放弃了缓存和代码复用页面一变大反而更慢。我的原则是只内联真正影响首屏的样式和极少量关键脚本其余仍然外链。对追求极致首屏的移动端活动页这个取舍尤其值得。3.4 一次真实的首屏白屏排查过程之前做过一个H5活动页打开就是白屏三四秒。检查网络面板发现HTML很小但页面引了六个同步脚本每个都要等前面一个执行完才能继续。改法很简单把不依赖顺序的脚本改成async把依赖DOM的脚本改成defer再把首屏不需要的两个统计脚本直接下放到路由注册之后。改完白屏时间降到一秒内。事后分析时特别感慨页面资源量一点没少只是调整了加载顺序和时机用户体验天差地别。关键渲染路径优化很多时候不是删代码而是让代码在对的时间出现。4. 图片、字体与DOM大幅减肥却常被忽略的三件事4.1 图片优化尺寸先于格式格式先于压缩图片往往占页面总流量的六成以上。我见过太多项目只做了一件事把PNG转成JPG。但图片优化的正确顺序是先砍尺寸再选格式最后才压质量。砍尺寸指的是实际显示尺寸。一个600像素宽的区域却加载2000像素宽的图片这在移动端尤为常见。用响应式图片的srcset和sizes属性可以让浏览器按设备宽度选择合适的图片或者让后端生成缩略图时控制尺寸。格式上现代浏览器基本都支持WebP和AVIF。WebP适合不透明和半透明图AVIF在同等画质下体积更小但解码稍慢。图标和小插画用SVG照片再考虑WebP。最后才是质量参数以视觉效果退化不明显为底线。切记不要在HTML里用CSS强行缩放大图那只是显示小了传输体积一点没少。4.2 字体体积与字体闪烁的平衡字体是军规里没细讲但现代站点绕不开的坑。一套中文自定义字体动辄几MB即使只取子集也要几百KB。很多页面因为字体没加载完成文字一直不显示或先显示默认字体再跳变。控制跳变的关键是font-display属性。设为swap文字先用本地字体显示下载完后替换虽然影响视觉稳定但至少不影响阅读设为block则会隐藏文字直到字体可用体验更差。更厉害的做法是字体子集化和预加载只打包页面用到的字符子集用preload把字体文件提前加载。如果对视觉一致性要求高我会考虑用系统字体栈。中文字体栈用现成的系统字体效果不差且零额外下载。商业项目需要品牌字体才值得投入子集化方案。4.3 DOM节点数为什么会影响性能35条里有一条减少DOM元素数量初看像玄学。其实原理很直接DOM树越大构建时间越长节点越多样式计算的匹配成本越高事件监听器挂在每个列表项上内存和回调开销也成倍增长。我处理过一个极端例子一个表格页动态渲染了上万行数据切页时卡到没法操作。把分页改成只渲染当前区域的数据滚动时再用虚拟列表技术回收离屏节点卡顿自己就好了。所以看到页面操作不跟手先别急着优化算法去数数页面有多少DOM节点、有没有已经滚出视野但仍然存在的节点。4.4 雪碧图与图标字体的退场与新用途雪碧图是当年为了把几十个小图标合并成一次请求。到了HTTP/2时代小图标直接用SVG加懒加载就可以不需要那套坐标计算。但如果是背景纹理或整张带透明区域的复杂图示雪碧图仍然有用因为单次请求仍然比几十次散图更省心。图标字体则会遇到字体加载延迟和无障碍标签难设置的问题。我的原则是纯粹的UI图标一律SVG特殊展示场景再考虑SVG Sprite。这两者都能把多个图标合在一个文件里兼顾请求数和可维护性。5. 脚本执行才是大头事件委托、DOM访问与内存抖动5.1 事件委托把上百个监听器变成一个列表页动态渲染几十条数据最容易犯的错是渲染一条就给它绑一个click。监听器数量一多内存和初始化时间都上涨。事件委托把监听器挂在父容器上通过事件冒泡判断真正点中的元素一个监听器就能管整个列表。实现上也很简单。父级监听click事件对象里有target和closest方法判断当前点击是否落在目标项上即可。还有一个容易被忽视的好处动态新增的元素不用重新绑事件因为事件处理在父级一直有效。对于高频交互的页面这几乎是个免费的性能提升点。5.2 批量读写DOM别让浏览器反复重排浏览器在读取某些布局属性前会强制把待定的样式计算刷一遍这个动作叫强制同步布局。如果代码里频繁写一个值又读一个值浏览器就会在两者之间反复重排性能损耗很明显。正确姿势是先把要改的值算好再一次性写入DOM需要读取布局数据时先统一读再统一改。用文档片段创建新节点整批插入用CSS的class切换而不是逐条改style。看起来都是小习惯但坦白说我遇到卡顿页面时检查完网络和渲染后八成能在这类代码里找到问题。对执行效率敏感的长列表和动画场景尤其明显。5.3 长任务与内存回收卡顿的元凶在哪首屏快不等于交互流畅。用户滚动页面时如果主线程被一个几十毫秒的脚本任务占住滚动手感就像慢动作。浏览器的Performance面板能清楚看到长任务时间线凡是超过50毫秒的任务都应该想办法拆小或者后移。内存问题就更隐蔽。全局变量、闭包、残留的事件监听器都可能让内存持续上涨最终触发卡顿甚至崩溃。我习惯在开发者工具的内存面板里拍堆快照对比操作前后的对象保留情况。尤其要注意图表、长列表和定时器里拉着不释放的引用它们经常是内存泄漏的头号嫌疑。6. 别让规范停留在文档里性能预算和自动化校验6.1 从军规到性能预算数字化的可执行规范35条军规再好如果只靠口头传阅过两周就没人当回事。我建议把规则翻译成具体数字形成性能预算。预算可以这样定首屏资源总大小不超过某个值、请求数不超过某个值、最大图片宽度有上限、第三方脚本只允许接入几个域名。预算不需要太完美先定一个让现有团队能接受的水平再逐步收紧。比如这个月首页产物先控制在一个合理范围内下个月再压20%。只有把快变成可测量的数字优化才不是个别前端自嗨。任何新增依赖都要回答一个问题它让预算增加了多少带来的价值值不值。6.2 自动化检查把预算写进发布流程才有效人工检查总会漏。我后来把所有性能预算都做成了自动化检查每次提交都跑一遍体积检查和关键指标扫描超过阈值就直接拦截。团队起初嫌烦后来发现这反而是好事新增代码如果拖慢页面第一时间就能发现不用等上线后被真实用户投诉。体积类检查可以用打包工具自带的分析插件网络层面的检查可以用无头浏览器跑一次关键指标。跑出来的结果除了硬性拦截还应该附上每次对比趋势让团队看到改动方向是否在变好。这样性能优化就从偶尔的紧急任务变成了日常开发的一部分。6.3 不要为了指标牺牲体验性能和业务的平衡最后说一点容易走极端的事。性能指标是手段不是最终目的。我见过团队为了把评分刷到满分把大量逻辑迁移到服务端结果首屏指标好看了交互延迟反而增加也见过为了减少请求数硬把单独缓存才有意义的业务模块揉进一个大文件更新一次连累用户下载所有变动。我的做法是指标只看趋势和体验外显不看虚高的总分。把预算的优先级排成白屏时长、可交互时间、滚动掉帧率、页面体积这一组跑分只能当参考。真正的验收标准是真实用户不抱怨、转化率不掉、开发者自己用起来顺手。军规也一样它是帮你定位问题的手册不是替你思考的说明书。最后分享一个我自己一直在用的小习惯把每次性能优化的改动记录下来注明当时现象、改动点、前后指标。不需要很复杂一两行备注就行。时间久了这些记录比任何军规清单都有参考价值因为你遇到的一定是你自己系统里的真实问题而不是又一个标准答案。