ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

WebKit工作流程深度解析:从HTML解析到页面渲染的性能优化实战

WebKit工作流程深度解析:从HTML解析到页面渲染的性能优化实战 浏览器内核这个词做前端或者搞移动端开发的人应该都不陌生。但真要让你把 WebKit 的工作流程从头到尾讲清楚很多人可能就卡壳了——知道它大概负责渲染知道 Safari 用它知道 Chrome 以前也用它但具体从一行 HTML 代码到屏幕上花花绿绿的像素中间到底经历了什么就说不太明白了。我自己在排查页面性能问题的时候不止一次因为对渲染流程理解不够深绕了不少弯路。比如早期遇到页面白屏时间过长第一反应是网络慢结果折腾半天才发现是 CSS 阻塞了渲染树的构建又比如做动画的时候发现掉帧严重以为是 JavaScript 写得不够优化最后定位到是频繁触发重排导致布局阶段反复计算。这篇文章就是把我这些年跟 WebKit 打交道积累下来的理解从头到尾梳理一遍。不管你是刚入行的前端新手还是已经写了几年业务代码想往深了走的老手又或者是做移动端 Hybrid 开发需要跟 WebView 打交道的工程师理解 WebKit 的工作流程都会让你在排查问题、做性能优化的时候更有底气。我会从 WebKit 的整体架构讲起然后一步步拆解从网络请求到页面呈现的完整链路中间穿插实际工作中踩过的坑和总结出来的经验。文章篇幅不短建议找个安静的时间慢慢看也可以当作一份参考手册遇到具体问题的时候翻到对应章节。1. WebKit 整体架构与核心模块拆解1.1 WebKit 到底是什么从浏览器引擎到渲染框架很多人会把 WebKit 和浏览器画等号其实不太准确。WebKit 是一个开源的浏览器引擎它提供的是从 HTML/CSS/JavaScript 解析到最终页面渲染的一整套能力但它本身不是一个完整的浏览器。浏览器还需要网络层、UI 层、插件系统、书签管理、隐私控制等等外围功能这些不属于 WebKit 的范畴。打个比方WebKit 就像是一辆车的发动机和传动系统它决定了车怎么跑、跑得多快、油耗多少但车的外观、座椅、空调、音响这些是整车厂另外加上去的。Safari 是苹果基于 WebKit 构建的完整浏览器Chrome 早期也用 WebKit后来分家搞了 Blink但 Blink 的底子还是 WebKit 那一套所以理解了 WebKit 的工作流程去看 Blink 的很多行为也能触类旁通。WebKit 的核心模块大致可以分成这几块解析器Parser负责把 HTML 和 CSS 文本变成 DOM 树和 CSSOM 树布局引擎Layout Engine负责计算每个元素在屏幕上的位置和大小渲染层Rendering Layer负责把布局结果绘制成位图JavaScript 引擎负责执行脚本WebKit 默认用的是 JavaScriptCoreChrome 用的是 V8网络层负责资源加载这块 WebKit 提供接口具体实现由各平台自己搞定。这几个模块之间的协作方式就是接下来要展开讲的工作流程。理解了这个流程你就能明白为什么 CSS 要放在 head 里、为什么 JavaScript 会阻塞渲染、为什么有时候改了 DOM 但页面没更新——这些问题的答案都藏在流程的细节里。1.2 为什么需要理解工作流程从排查问题说起我刚开始做前端那几年遇到页面显示异常基本靠猜和试。样式不对就调样式脚本报错就看控制台实在不行就一行行注释代码排查。这种方式在项目小的时候还能应付但项目一大组件一多就完全不够用了。后来有一次遇到一个很诡异的问题页面在 Safari 上正常在某个 Android 的 WebView 里却出现了元素错位。排查了很久才发现那个 WebView 用的 WebKit 版本比较老对某些 CSS 属性的布局计算方式和新版本不一样。如果当时我对 WebKit 的布局流程有清晰的认识就能很快定位到是布局阶段的问题而不是在样式代码里瞎找。再举个例子做首屏性能优化的时候大家都知道要减少阻塞渲染的资源。但具体哪些资源阻塞、阻塞发生在哪个阶段、怎么衡量阻塞的影响这些都需要对工作流程有理解才能回答。比如 CSS 是阻塞渲染的但它不阻塞 HTML 解析JavaScript 既阻塞解析又阻塞渲染除非加上 async 或 defer。这些结论背后都是 WebKit 的具体流程在起作用。所以我的观点是理解 WebKit 的工作流程不是为了应付面试而是为了在遇到问题的时候能有一套清晰的排查思路知道该往哪个方向去找原因。这比记住几个优化技巧要有价值得多。1.3 核心模块的职责边界与协作关系WebKit 的各个模块之间不是孤立的它们通过明确的接口和事件机制协作。我画不了图但可以用文字描述一下这个协作链路。当你在地址栏输入一个 URL 并回车浏览器进程会发起网络请求拿到 HTML 文本后交给 WebKit。WebKit 的 HTML 解析器开始工作一边解析一边构建 DOM 树。解析过程中如果遇到link标签引用外部 CSS会发起 CSS 请求遇到script标签会暂停解析去下载并执行脚本除非有 async/defer 标记。CSS 下载完成后CSS 解析器构建 CSSOM 树。DOM 树和 CSSOM 树都准备好之后渲染树Render Tree开始构建然后进入布局阶段计算位置最后进入绘制阶段生成位图交给合成器上屏。这个链路里每个模块的产出都是下一个模块的输入。DOM 树和 CSSOM 树是布局的输入布局结果是绘制的输入绘制结果是合成的输入。任何一个环节出问题都会影响最终的显示效果。比如 DOM 树构建不完整渲染树就缺节点CSSOM 没准备好渲染树就没法确定样式布局计算错误绘制出来的位置就不对。理解这个协作关系的好处是当你看到页面显示异常时可以按照链路顺序去排查先看 DOM 结构对不对再看样式有没有生效然后看布局位置是否合理最后看绘制有没有问题。这比漫无目的地试要高效得多。2. 从输入 URL 到页面呈现的完整流程2.1 网络请求与 HTML 文本的获取用户在地址栏输入 URL 之后浏览器首先要做的是找到这个 URL 对应的服务器并把 HTML 文本拿回来。这一步涉及 DNS 解析、TCP 连接建立、HTTP 请求发送、响应接收等环节。虽然这些不属于 WebKit 的核心职责但它们是整个流程的起点而且网络层的表现会直接影响后续所有阶段的时机。DNS 解析是把域名变成 IP 地址的过程。浏览器会先查自己的 DNS 缓存没有的话再查操作系统的缓存还没有就向 DNS 服务器发起查询。这一步的耗时取决于网络环境和缓存命中情况通常第一次访问某个域名会慢一些后续访问因为有缓存会快很多。TCP 连接建立就是常说的三次握手。如果是 HTTPS还需要额外的 TLS 握手这会增加几个往返的耗时。连接建立之后浏览器发送 HTTP 请求服务器返回响应。响应头里会包含 Content-Type、Content-Length、缓存策略等信息这些信息会影响 WebKit 后续怎么处理这个资源。拿到 HTML 文本后WebKit 并不会等整个文件下载完才开始解析而是边下载边解析。这就是所谓的流式解析。这样做的好处是如果 HTML 文件很大用户不用等到全部下载完就能看到部分内容。但这也带来一个问题如果解析到一半发现需要某个资源比如 CSS 或 JavaScript解析可能会被阻塞后面的内容就得等着。实操心得做首屏优化的时候把关键的 HTML 结构放在文件前面把不重要的脚本放到后面或者用 defer 加载就是利用流式解析的特性让用户尽快看到核心内容。2.2 HTML 解析与 DOM 树构建HTML 解析是 WebKit 工作流程里第一个核心环节。解析器拿到 HTML 文本后会按照 HTML 规范逐字符扫描识别出标签、属性、文本内容然后构建出一棵 DOM 树。DOM 树是页面结构的抽象表示每个 HTML 元素对应树上的一个节点节点之间有父子、兄弟关系。解析过程中有几个关键点需要注意。第一HTML 解析是容错的。也就是说即使你的 HTML 写得不太规范比如标签没闭合、嵌套顺序不对解析器也会尽量纠正构建出一棵合理的 DOM 树。这是 HTML 和 XML 的一个重要区别XML 解析器遇到格式错误会直接报错而 HTML 解析器会尝试修复。第二解析过程中遇到script标签会暂停解析。这是因为 JavaScript 可能会通过 document.write 修改 DOM 结构如果解析器不暂停就可能导致 DOM 树和脚本的预期不一致。所以默认情况下脚本是阻塞解析的。要避免这种阻塞可以给 script 标签加上 async 或 defer 属性。async 表示脚本下载不阻塞解析下载完立即执行执行时仍然阻塞解析defer 表示脚本下载不阻塞解析等解析完成后再按顺序执行。第三解析过程中遇到link标签引用外部 CSS不会暂停 HTML 解析但会阻塞渲染。也就是说DOM 树可以继续构建但渲染树要等 CSSOM 准备好才能开始构建。这就是为什么 CSS 被称为渲染阻塞资源。注意事项很多人会把“阻塞解析”和“阻塞渲染”搞混。简单来说JavaScript 默认两者都阻塞CSS 只阻塞渲染不阻塞解析。理解这个区别对优化页面加载速度很关键。2.3 CSS 解析与 CSSOM 树构建CSS 解析器的工作是把 CSS 文本变成 CSSOM 树。CSSOM 树的结构和 DOM 树类似但它是用来描述样式的。每个节点包含了对应元素的计算样式信息。CSSOM 的构建过程比 DOM 要复杂一些因为 CSS 有层叠、继承、优先级这些规则解析器需要处理这些规则才能确定每个元素的最终样式。层叠是指多个样式规则可能同时作用于同一个元素需要按照一定的优先级来决定哪个规则生效。优先级由选择器的特异性、规则的来源用户代理样式、作者样式、用户样式、以及 !important 标记共同决定。继承是指某些属性会从父元素传递给子元素比如 color、font-size 这些。解析器在处理每个节点的时候需要把这些规则都考虑进去计算出最终样式。CSSOM 构建的一个特点是它必须等所有 CSS 都下载并解析完才能完成。这是因为后面的 CSS 规则可能会覆盖前面的规则如果不等全部解析完就确定样式可能会得到错误的结果。所以外部 CSS 的加载时间会直接影响渲染的开始时间。这也是为什么推荐把关键 CSS 内联到 HTML 里非关键 CSS 异步加载。内联的 CSS 不需要额外的网络请求可以更快地参与 CSSOM 构建从而让渲染更早开始。非关键 CSS 可以用 media 属性或者 JavaScript 动态加载避免阻塞首屏渲染。2.4 渲染树构建DOM 与 CSSOM 的合并DOM 树和 CSSOM 树都准备好之后WebKit 会把它们合并成一棵渲染树Render Tree。渲染树上的节点叫做渲染对象Render Object每个渲染对象对应一个需要显示在屏幕上的元素。注意不是所有 DOM 节点都会出现在渲染树上。比如head标签、display: none的元素、script标签这些不需要显示的内容就不会生成渲染对象。渲染树的构建过程大致是这样的从 DOM 树的根节点开始遍历对每个可见节点找到它在 CSSOM 中对应的样式信息然后创建一个渲染对象。渲染对象里包含了这个元素的位置、大小、样式等所有渲染需要的信息。如果某个节点有子节点就递归处理子节点把子渲染对象挂到父渲染对象下面。这里有一个容易混淆的点渲染树上的节点和 DOM 树上的节点不是一一对应的。除了 display: none 的元素不会出现在渲染树上之外有些元素可能会生成多个渲染对象比如行内元素跨行显示的时候可能会被拆成多个渲染对象。还有一些伪元素::before、::after也会生成渲染对象但它们在 DOM 树里没有对应的节点。实操心得做性能优化的时候减少渲染树上的节点数量是一个有效的手段。比如虚拟列表就是通过只渲染可视区域内的元素大幅减少渲染对象数量从而提升滚动性能。2.5 布局计算确定每个元素的位置和大小渲染树构建完成后进入布局阶段。布局阶段的任务是计算每个渲染对象在屏幕上的确切位置和大小。这个过程也叫重排Reflow。布局是一个递归的过程从根渲染对象开始逐层计算每个节点的几何信息。布局计算需要考虑很多因素元素的盒模型margin、border、padding、content、定位方式static、relative、absolute、fixed、浮动和清除浮动、flex 和 grid 布局、文本的换行和尺寸等等。这些因素相互影响所以布局计算往往需要多次遍历才能确定最终结果。布局阶段是渲染流程里最耗时的环节之一。因为它涉及大量的计算而且一旦某个元素的布局发生变化可能会影响到其他元素的布局导致连锁反应。比如你修改了一个元素的宽度它旁边的元素可能需要重新排列它下面的元素可能需要上移或下移整个页面的布局都可能需要重新计算。这也是为什么频繁触发重排会导致性能问题。每次修改 DOM 结构、修改影响布局的样式属性、改变窗口大小、滚动页面某些情况下都可能触发重排。重排的范围可能是整个页面也可能只是某个子树取决于修改影响的区域。注意事项能用 transform 和 opacity 做的动画就不要用 width、height、top、left 这些属性。因为 transform 和 opacity 可以只触发合成不触发重排和重绘性能要好得多。这是我在做动画优化时最常用的一条经验。2.6 绘制与合成从位图到屏幕像素布局完成后进入绘制阶段。绘制阶段的任务是把每个渲染对象转换成屏幕上的像素。WebKit 会把页面分成多个图层Layer每个图层单独绘制成位图然后由合成器Compositor把这些图层合成最终的画面。分层是 WebKit 渲染的一个重要机制。不是所有元素都会单独成层通常只有需要独立变换或动画的元素、有 3D 变换的元素、video 和 canvas 等才会被提升为独立图层。分层的目的是为了减少重绘的范围。如果一个元素在独立图层上它发生变化时只需要重绘这个图层不影响其他图层。合成阶段是把各个图层的位图按照正确的顺序和位置合并成最终画面。这个阶段通常由 GPU 来完成所以速度很快。合成器还会处理滚动、缩放、动画等操作这些操作如果只涉及合成不涉及布局和绘制就可以在 GPU 上高效完成不占用主线程。理解绘制和合成的一个关键是重排一定导致重绘重绘不一定导致重排合成不一定导致重绘。也就是说修改布局属性会触发重排和重绘修改绘制属性如颜色、背景会触发重绘但不触发重排修改合成属性如 transform、opacity只触发合成不触发重绘和重排。这个层级关系是性能优化的基础。3. 关键机制深度解析与实操要点3.1 阻塞机制为什么 CSS 和 JavaScript 会影响渲染阻塞机制是 WebKit 工作流程里最容易被误解的部分。很多人知道 CSS 和 JavaScript 会阻塞渲染但具体怎么阻塞、阻塞什么、怎么避免就说不太清楚了。我在这里把这个问题彻底讲透。先看 CSS。外部 CSS 文件会阻塞渲染但不阻塞 HTML 解析。也就是说当解析器遇到link relstylesheet时它会发起 CSS 请求但继续解析后面的 HTML。DOM 树可以正常构建但渲染树要等 CSSOM 构建完成才能开始。所以如果 CSS 加载很慢用户会看到白屏因为渲染树还没开始构建没有任何内容可以显示。再看 JavaScript。默认情况下script标签会阻塞 HTML 解析。解析器遇到 script 标签时会暂停解析下载脚本如果是外部脚本然后执行脚本执行完再继续解析。这是因为脚本可能会修改 DOM 结构如果解析器不暂停就可能导致 DOM 树和脚本的预期不一致。脚本执行期间渲染也是被阻塞的。要避免 JavaScript 阻塞解析可以用 async 或 defer。async 脚本的下载不阻塞解析下载完成后立即执行执行时阻塞解析。defer 脚本的下载不阻塞解析等 HTML 解析完成后再按顺序执行。两者的区别在于执行时机async 是下载完就执行执行顺序不确定defer 是解析完后按顺序执行顺序确定。实操心得对于不依赖 DOM 结构、独立运行的脚本比如统计代码用 async对于需要操作 DOM、有依赖关系的脚本用 defer。如果脚本必须在解析过程中执行比如 document.write那就只能让它阻塞了但这种情况应该尽量避免。3.2 重排与重绘触发条件与性能影响重排和重绘是前端性能优化里绕不开的话题。重排是指布局发生变化需要重新计算元素的位置和大小重绘是指外观发生变化需要重新绘制像素。两者的性能开销不同重排的开销通常比重绘大得多因为重排可能影响整个页面的布局。触发重排的操作包括修改 DOM 结构增删节点、修改影响布局的样式属性width、height、margin、padding、position、display 等、改变窗口大小、滚动页面某些情况下、读取某些布局属性offsetTop、scrollTop、getComputedStyle 等。注意最后一条读取布局属性也会触发重排因为浏览器需要确保返回的值是最新的所以会强制进行一次布局计算。触发重绘的操作包括修改颜色、背景、边框颜色、visibility 等不影响布局的样式属性。重绘不需要重新计算布局只需要重新绘制像素所以开销相对较小。这里有一个经典的性能陷阱布局抖动Layout Thrashing。如果你在循环里交替进行读取布局属性和修改布局属性浏览器会被迫在每次读取时都重新计算布局导致性能急剧下降。比如这样的代码// 错误示范布局抖动 for (let i 0; i elements.length; i) { const height elements[i].offsetHeight; // 读取触发重排 elements[i].style.height height 10 px; // 修改触发重排 }正确的做法是把读取和修改分开先批量读取再批量修改// 正确示范批量读写 const heights []; for (let i 0; i elements.length; i) { heights.push(elements[i].offsetHeight); // 批量读取 } for (let i 0; i elements.length; i) { elements[i].style.height heights[i] 10 px; // 批量修改 }注意事项现代浏览器对重排重绘做了一些优化比如把多次修改合并成一次但布局抖动这种模式仍然会导致性能问题。用 requestAnimationFrame 来组织动画相关的读写操作是一个好习惯。3.3 图层合成与硬件加速的取舍图层合成是 WebKit 渲染流程里比较高级的部分也是性能优化的一个重要手段。前面提到WebKit 会把页面分成多个图层每个图层单独绘制然后由合成器合成。如果一个元素被提升为独立图层它的变化就只需要重绘这个图层不影响其他图层。什么情况下元素会被提升为独立图层常见的有有 3D 变换transform: translateZ(0) 或 translate3d、有 will-change 属性、是 video 或 canvas 元素、有 opacity 动画、有 position: fixed 等。开发者也可以通过 CSS 属性主动触发图层提升比如transform: translateZ(0)或will-change: transform。图层提升的好处是动画和变换可以在 GPU 上高效完成不占用主线程也不触发重排重绘。但图层提升不是越多越好。每个图层都需要占用内存图层太多会导致内存占用过高在移动设备上可能引发崩溃。而且图层合成本身也有开销图层太多反而会降低性能。实操心得图层提升要按需使用。对于需要频繁动画的元素可以提升为独立图层对于静态元素没必要提升。用 Chrome DevTools 的 Layers 面板可以查看页面的图层情况帮助判断哪些元素被提升了哪些不该提升的被提升了。3.4 JavaScript 引擎与渲染流程的交互JavaScript 引擎WebKit 里是 JavaScriptCore和渲染流程的交互是理解 WebKit 工作流程的一个关键点。JavaScript 代码执行的时候可能会修改 DOM、修改样式、发起网络请求这些操作都会影响渲染流程。JavaScript 是单线程执行的和渲染流程共享同一个主线程。这意味着如果 JavaScript 执行时间过长渲染就会被阻塞用户会感觉到页面卡顿。这就是为什么长任务Long Task会导致性能问题。一个长任务如果超过 50 毫秒就可能导致掉帧因为浏览器需要在 16 毫秒内完成一帧的渲染如果 JavaScript 占用了太多时间渲染就来不及了。JavaScript 修改 DOM 或样式后并不会立即触发重排重绘。浏览器会把这些修改记录下来等到合适的时机批量处理。这个时机通常是在当前 JavaScript 执行栈清空之后浏览器会检查是否有待处理的渲染更新如果有就执行渲染流程。这就是所谓的异步渲染。理解这一点很重要如果你在 JavaScript 里修改了 DOM然后立即读取布局属性浏览器会强制进行一次同步布局计算以确保返回的值是最新的。这就是前面提到的布局抖动的根源。所以在修改 DOM 之后如果需要读取布局属性最好等到下一帧再读或者用 requestAnimationFrame 来组织代码。注意事项requestAnimationFrame 的回调会在下一帧渲染之前执行适合用来做动画和批量 DOM 操作。把读写操作放在 requestAnimationFrame 里可以避免布局抖动也能保证动画的流畅性。4. 常见问题排查与性能优化实录4.1 页面白屏时间过长从渲染阻塞入手页面白屏是前端开发里最常见的问题之一。用户打开页面看到的是一片空白等了几秒才出现内容。这个问题通常和渲染阻塞有关。排查白屏问题的第一步是看网络请求的时间线。用浏览器的开发者工具打开 Network 面板看 HTML、CSS、JavaScript 的加载时间。如果 HTML 加载很慢那是网络或服务器的问题如果 HTML 很快但 CSS 很慢那就是 CSS 阻塞了渲染如果 CSS 很快但 JavaScript 很慢那就是 JavaScript 阻塞了解析和渲染。我遇到过一个典型案例一个页面的首屏白屏时间长达 5 秒。排查发现HTML 文件本身只有几 KB加载很快但 head 里引用了一个很大的 CSS 框架文件有 2MB 多而且服务器没有开启压缩下载花了 3 秒多。CSS 下载完之后又有一个同步的 JavaScript 文件要加载和执行又花了 1 秒多。整个过程中渲染一直被阻塞用户看到的就是白屏。解决方法是把关键 CSS 内联到 HTML 里非关键 CSS 异步加载JavaScript 加上 defer 或 async开启服务器压缩和缓存。优化之后白屏时间降到了 1 秒以内。实操心得判断哪些 CSS 是关键 CSS可以看首屏渲染需要哪些样式。通常布局框架、首屏组件的样式是关键 CSS弹窗、下拉菜单、非首屏内容的样式可以异步加载。用工具如 Critical 可以自动提取关键 CSS。4.2 动画卡顿掉帧重排重绘的锅动画卡顿是另一个常见问题。用户滑动页面或者触发动画时感觉不流畅有明显的掉帧。这个问题通常和重排重绘有关。排查动画卡顿可以用 Chrome DevTools 的 Performance 面板录制一段动画过程然后看帧率。如果帧率低于 60fps说明有性能问题。再看具体是哪个环节耗时最多如果是 Layout 耗时长说明有重排如果是 Paint 耗时长说明有重绘如果是 Composite 耗时长说明合成有问题。我遇到过一个案例一个列表页的滚动动画很卡。排查发现列表项的 hover 效果用了box-shadow和border的变化这些属性会触发重绘。而且列表项没有独立图层每次重绘都影响整个列表。解决方案是把 hover 效果改成用transform和opacity实现并把列表项提升为独立图层。优化之后滚动流畅了很多。注意事项做动画的时候尽量只使用 transform 和 opacity 这两个属性。它们可以只触发合成不触发重排和重绘性能最好。如果必须用其他属性考虑用 will-change 提前告诉浏览器让浏览器做好准备。4.3 内存占用过高图层太多的代价图层提升可以提升性能但图层太多会导致内存占用过高。这个问题在移动设备上尤其明显因为移动设备的内存有限图层太多可能导致页面崩溃。排查内存问题可以用 Chrome DevTools 的 Memory 面板看页面的内存占用情况。也可以用 Layers 面板看有多少个图层每个图层占多少内存。如果图层数量很多而且很多是不必要的就需要优化。我遇到过一个案例一个页面在低端 Android 手机上经常崩溃。排查发现页面里有很多元素都加了transform: translateZ(0)导致图层数量过多内存占用过高。解决方案是去掉不必要的图层提升只对真正需要动画的元素提升图层。优化之后内存占用降了一半崩溃问题也解决了。实操心得图层提升是一把双刃剑用得好能提升性能用不好会带来内存问题。我的经验是只对需要频繁动画的元素提升图层静态元素不要提升。用 will-change 比用 translateZ(0) 更语义化也更容易管理。4.4 常见问题速查表为了让大家在遇到问题的时候能快速定位我整理了一个常见问题速查表问题现象可能原因排查方向解决方案页面白屏时间长CSS 或 JavaScript 阻塞渲染看 Network 面板的资源加载时间内联关键 CSS异步加载非关键 CSS脚本加 defer/async动画卡顿掉帧重排或重绘频繁用 Performance 面板看 Layout 和 Paint 耗时用 transform 和 opacity 做动画提升图层滚动不流畅滚动触发重排或重绘看滚动时的帧率和 Layout 耗时用 passive 事件监听避免在滚动回调里读写布局内存占用过高图层太多或 DOM 节点太多用 Memory 和 Layers 面板查看减少不必要的图层提升用虚拟列表减少 DOM 节点布局抖动交替读写布局属性看代码里是否有循环读写批量读取批量修改用 requestAnimationFrame首屏渲染慢渲染树构建被阻塞看 CSSOM 和 DOM 的构建时间减少关键资源优化 CSS 选择器避免深层嵌套这个表里的每一行都是我在实际工作中遇到过的问题。当然具体问题还要具体分析这个表只是一个排查的起点帮你快速缩小范围。4.5 性能优化的几个实用原则最后分享几条我在实践中总结的性能优化原则这些原则都是围绕 WebKit 的工作流程来的减少关键渲染路径的长度。关键渲染路径是指从 HTML 开始加载到首屏渲染完成的这个过程。路径越短首屏越快。具体做法包括减少关键资源数量、减小关键资源体积、优化资源加载顺序。避免不必要的重排重绘。修改样式的时候尽量用不影响布局的属性。做动画的时候尽量用 transform 和 opacity。批量操作 DOM避免频繁的读写交替。合理使用图层提升。对需要频繁动画的元素提升图层静态元素不要提升。用 will-change 而不是 translateZ(0)更语义化也更好管理。控制 DOM 规模。DOM 节点越多渲染树越大布局和绘制的开销也越大。用虚拟列表、懒加载等手段控制 DOM 规模。利用浏览器缓存。合理设置缓存策略减少重复的网络请求。对于不常变化的资源设置较长的缓存时间对于经常变化的资源用文件名哈希或版本号来管理缓存。这些原则看起来简单但真正做到位需要对这些原则背后的原理有理解。比如为什么减少关键渲染路径的长度能提升首屏速度这就涉及到渲染阻塞的机制为什么 transform 和 opacity 性能好这就涉及到图层合成和硬件加速。理解了原理才能在实际场景中灵活运用而不是死记硬背几条规则。我在实际项目里做性能优化的时候通常会先用工具测量找到瓶颈在哪里然后针对性地优化。优化完之后再测量确认效果。这个过程可能需要反复几次但每次都能学到新的东西。WebKit 的工作流程虽然复杂但一旦理解了很多问题都会变得清晰起来。
返回列表