作者:rosefang,腾讯 PCG 前端开发工程师
当我们在做性能优化的时候,我们究竟在优化什么?浏览器底层是一个什么架构?浏览器渲染的本质究竟是什么?哪些方面对用户的体验影响才是最大的?有没有业内一些通用的标准或标杆参考?都 1202 年了,雅虎军规还有没有用?性能分析工具都有哪些?我们怎么进行打点分析才是合适的?
本文为你一一讲解这些。了解了这些问题,可能你在做性能优化的时候才能更加得心应手。
性能优化的目的,就是为了提供给用户更好的体验,这些体验包含这几个方面:展示更快、交互响应快、页面无卡顿情况。
更详细的说,就是指,在用户输入 url 到站点完整把整个页面展示出来的过程中,通过各种优化策略和方法,让页面加载更快;在用户使用过程中,让用户的操作响应更及时,有更好的用户体验。
对于前端工程师来说,要做好性能优化,需要理解浏览器加载和渲染的本质。理解了本质原理,才能更好的去做优化。所以我们先来看看浏览器架构是怎样的。
从大的方面来说,浏览器是一个多进程架构。
它可以是一个进程包含多个线程,也可以是多个进程中,每个进程有多个线程,线程之间通过 IPC 通讯。每个浏览器有不同的实现细节,并没有标准规定浏览器必须如何去实现。
这里我们只谈论 chrome 架构。
下面这张图是目前 chrome 的多进程架构图。
我们来看看这些进程分别对应浏览器窗口中的哪一部分:
那么,怎么看浏览器对应启动了什么进程呢?
chrome 中,我们可以通过更多->More Tools->Task Manager 看到启动的进程。
从 chrome 官网和源码,我们也可以得知,多进程架构中包含这些进程:
这里的话只是简单介绍一下浏览器的多进程架构,让大家对浏览器整体架构有个初步认识,其实背后的细节还有很多,这里就不一一展开。有兴趣可以细看这一系列文章和chrome 官网介绍。
从以上的多架构,我们了解到,与前端渲染、性能优化相关的,其实主要是 Renderer 进程和 GPU 进程。那么,它们又是什么架构呢?
来看一下这张我们再熟悉不过的图。
Renderer 进程中 3 个线程的作用为:
当用户在快速滑动过程中,如果主线程没有绑定事件,Compositor 线程是可以快速响应并绘制的,这是浏览器做的一个优化。
Service Workers 和 Web Workers 可以暂时理解也在 Renderer 进程中,这里不展开讨论。
主线程需要重点讲下。因为这是我们的代码真实存在的环境。
从上一小节 Render 进程和 GPU 进程的图中,我们可以看到有个红色的箭头,从 Recal Styles 和 Layout 指向了 requestAnimationFrame,这意味着有 Forced Synchronous Layout (or Styles)(强制回流和重绘)发生,这一点在性能方面特别要注意。
在 Main Thread 中,有这几个需要注意一下:
主线程的顺序始终都是:
Input Event Handler->requestAnimationFrame->ParseHtml->ReculateStyles->Layout- >Update Layer Tree->Paint->Composite->commit->requestIdleCallback
只能从前往后,例如,必须先是 ReculateStyles,然后 Layout、然后 Paint。但是,如果它只需要做最后一步 Paint,那么这就是它全部要做的事情,不会再发生前面的 ReculateStyles 和 Layout。
这里其实给了我们一个启示:如果要让 fps 保持 60,即每帧的 js 执行时间少于 16.66ms,那么让这个主线程执行的过程尽可能地少,是我们的性能优化目标。
根据主线程的这些步骤,理想的情况下,我们只希望浏览器只发生最后一个步骤:Composite(合成)。
CSS 的属性是我们需要关注一下的模块。这里有描述了哪些CSS 属性会引起重绘、回流和合成。例如,让我们给一个元素进行移动位置时:transform
和opacity
可以直接触发合成,但是left
和top
却会触发 Layout、Paint、Composite3 个动作。所以显然用 transform 时更好的方案。
但这并不是说我们不应该用 left 和 top 这些可能引起重绘回流的属性,而是应该关注每个属性在浏览器性能中引起的效果。
多年前雅虎的 Nicolas C. Zakas 提出 7 个类别 35 条军规,至今为止很多前端优化准则都是围绕着这个展开。如果严格按照这些规则去做,其实我们有很多优化工作可以做,只要认真践行,性能提升不是问题。
我们来看看它 7 个分类都是围绕哪些方面展开:
从上面的描述可以看到,其实雅虎军规,是围绕页面发起请求那一刻,到页面渲染完成,页面开始交互这几个方面来展开,提出的一些原则。
很多原则大家也都耳熟能详,就不全部展开了,有兴趣的同学可以去查看原文。这里主要想提一些忽略但是又值得注意的点:
减少 DOM 节点数量
为什么要减少 DOM 节点的数量?
当遍历查询 500 和 5000 个 DOM 节点,进行事件绑定时,会有所差别。
当一个页面 DOM 节点过多,应该考虑使用无限滚动方案来使视窗节点可控。可以看看google 提的方案。
减少 cookie 大小
cookie 传输会造成带宽浪费,影响响应时间,可以这样做:
消除不必要的 cookies;
静态资源不需要 cookie,可以采用其他的域名,不会主动带上 cookie。
避免图片 src 为空
图片 src 为空时,不同浏览器会有不同的副作用,会重新发起一起请求。
要衡量性能,我们必须有一些客观的、可衡量的指标来进行监控。但是客观且定量可衡量的指标不一定能反映用户的真实体验。
以前,我们会用 load 事件的触发来衡量一个页面是否加载或显示完成。但是设想会不会有这样的情况:一个页面的 load 事件已经被触发,但是却在 load 事件之后几秒才开始加载内容和渲染页面,所以这个时候,load 事件并不能真实反映用户看到内容的时刻。
在过去几年,google 团队和W3C 性能工作组致力于提供标准的性能 API 来真正衡量用户的体验。主要是从这 4 个方面思考:
思考点 | 详细内容 |
---|
通常有 2 种途径来衡量性能。
有这几个类型与用户感知性能相关。
对应上面几种分类,Google 和 W3C 性能工作组提供了对应这几种性能指标:
这些指标能从一定程度上衡量页面性能,但不一定都是有效的。举个例子。LCP 指标主要用户衡量页面的主要内容是否完成加载,但会有这样的情况,最大的元素并不是主要内容,那么这个时候 LCP 指标并不是那么重要。
每个不同的站点有自己的特殊性,可以参考以上角度进行衡量,也需要因地制宜。
在以上列出的指标中,Google 定义了 3 个最核心的指标,作为 Core Web Vitals。它们分别代表着:加载、交互、视觉稳定性。
当页面访问量有 75%的数据达到了以上以上 Good 的标准,则认为性能是不错的了。
Core Web Vitals 是作为核心性能指标,但是其他指标也同样在重要,是做为核心指标的一个辅助。例如,TTFB 和 FCP 都可以用来衡量加载性能(服务器响应时间和渲染时间),它们作为 LCP 的一个问题手段辅助。同样的,TBT 和 TTI 对于衡量交互性能也很重要,是 FID 的一个辅助,但是它们无法在线上进行测量,也无法反映以用户为中心的结果。
Google 官方提供了一个web-vitals库,线上或本地都可以测量上面提到的 3 个指标:
import{getCLS, getFID, getLCP}from 'web-vitals';
function sendToAnalytics(metric){
const body=JSON.stringify(metric);
// Use `navigator.sendBeacon()` if available, falling back to `fetch()`.
(navigator.sendBeacon && navigator.sendBeacon('/analytics', body)) ||
fetch('/analytics',{body, method: 'POST', keepalive: true});
}
getCLS(sendToAnalytics);
getFID(sendToAnalytics);
getLCP(sendToAnalytics);
下面,分别讲讲这 3 个指标定义的原因、如何测量、如何优化。
LCP 是指页面开始加载到最大文本块内容或图片显示在页面中的时间。那么哪些元素可以被定义为最大元素呢?
<img>
标签<image>
在 svg 中的 image 标签<video>
video 标签线上测量工具
web-vitals JavaScript library
实验室工具
原生的 JS API 测量
LCP 还可以用 JS API 进行测量,主要使用 PerformanceObserver 接口,目前除了 IE 不支持,其他浏览器基本都支持了。
new PerformanceObserver((entryList)=>{
for (const entry of entryList.getEntries()){
console.log('LCP candidate:', entry.startTime, entry);
}
}).observe({type: 'largest-contentful-paint', buffered: true});
我们看一下结果是怎样的:
Google 官方 web-vitals 库
Google 官方也提供了一个web-vitals库,底层还是使用这个 API,只是帮我们处理了一些需要测量和不需测量的场景、以及一些细节问题。
LCP 可能被这四个因素影响:
更加详细的优化建议就不展开了,可以参考这里。
我们都知道第一印象的重要性,比如初次遇到某人形成的印象,会在后续交往中起重要的影响。对于一个网站也是如此。
网站以多快的速度加载完成是其中一项指标,加载后以多快的速度对用户进行响应也同样重要。FID 就是指后者。
可以通过下面的图来更详细了解 FID 处于哪个位置:
从上图可以看出,当主线程处于繁忙的时候,FID 是指从浏览器接收到了用户输入,到浏览器对用户的输入进行响应的延迟时间。
通常,当我们在写代码的时候,会认为只要用户输入信息,我们的事件回调就会立刻响应,但实际上并不是这样。这是主线程可能处于繁忙,浏览器正忙着解析和执行其他 js。如上图所示的 FID 时间,主线程正在处理其他任务。
当 FID 的时间为 100ms 或以内,则为 Good。
上面的例子中,用户刚好在主线程最繁忙的时刻进行了交互,但是如果用户在主线程空闲的时候交互,那么浏览器可以立刻响应。所以 FID 的值需要重点查看它的分布情况。
FID 实际上测量的是输入事件被感知到到主线程空闲的这段时间。这意味着即使没有输入事件被注册,FID 也可以测量。因为用户的输入相应并不一定需要事件被执行,但一定需要主线程是空闲的。例如,下面这些 HTML 元素都需要在交互响应之前等待主线程上的正在执行的任务完成:
<input>
、<textarea>
、<radio>
、<checkbox>
<select>
<a>
为什么要考虑测量第一次的输入延迟?有如下原因:
为什么 FID 只是包含从用户输入到主线程开始相应的时间?而没有包含事件处理到浏览器绘制 UI 的时间?
尽管主线程处理和绘制的这段时间也很重要,但是如果 FID 把这段时间也包含进来,开发者可能会使用异步 API(例如setTimeout
、requestAnimationFrame
)来把这个 task 拆分到下一帧,以较少 FID 的时间,这样不仅没有提高用户体验,反而使用户体验降低。
FID 可以在实验环境也可以在线上环境测量。
线上测量工具
web-vitals JavaScript library
原生的 JS API 测量
new PerformanceObserver((entryList)=>{
for (const entry of entryList.getEntries()){
const delay=entry.processingStart - entry.startTime;
console.log('FID candidate:', delay, entry);
}
}).observe({type: 'first-input', buffered: true});
PerformanceObserver 目前除了在 IE 上没有兼容,其他浏览器基本都兼容了。
我们看一下结果是怎样的:
Google 官方 web-vitals 库
Google 官方也提供了一个web-vitals库,底层还是使用这个 API,只是帮我们处理了一些需要测量和不需测量的场景、以及一些细节问题。
FID 可能被这四个因素影响:
更加详细的优化建议可以参考这里。
CLS 是一个非常重要的、以用户为中心的测量指标。它能衡量页面是否排版稳定。
页面移动会经常发生在资源异步加载、或者 DOM 元素动态添加到已存在的页面元素上面。这些元素有可能是图片、视频、第三方广告或小图标等。
但是我们开发过程中可能不会察觉到这些问题,因为调试过程中刷新页面,图片都已经缓存在本地。调试接口的时候我们使用的是 mock 或者在局域网,接口速度都很快,这些延迟都可能被我们忽略。
CLS 就是帮我们去发现这些真实发生在用户端的问题的指标。
CLS 是测量页面生命周期中,每个发生意外布局移动的分数。当一个可视元素在下一帧移动到另外一个位置,就是指布局移动。
CLS 的分数在 0.1 或以下,则为 Good。
那么意外布局移动的分数如何计算?
浏览器会监控两桢之间发生移动的不稳定元素。布局移动分数由 2 个元素决定:impact fraction 和 distance fraction。
layout shift score=impact fraction * distance fraction
可视区域内,在前一帧到下一帧之间所有不稳定的元素的并集,会影响当前帧的布局移动分数。
举个例子,下面这张图中,左边是当前帧的一个元素,下一帧中,元素下移了可视区域内 25%的高度。红色虚线框标出了两桢中当前元素的并集,占适口的 75%,所以这个时候,impact faction 是 0.75。
另外一个影响布局移动分数的是 distance fraction,指这个元素相对视口移动的距离。不管是横向还是竖向,取最大值。
下面例子中,竖向距离更大,该元素相对适口移动了 25%的距离,所以 distance fraction 是 0.25。所以布局移动分数是 0.75 * 0.25=0.1875
.
但是要注意的是,并不是所有的布局移动都是不好的,很多 web 网站都会改变元素的开始位置。只有当布局移动是非用户预期的,才是不好的。
换句话说,当用户点击了按钮,布局进行了改动,这是 ok 的,CLS 的 JS API 中有一个字段hadRecentInput
,用来标识 500ms 内是否有用户数据,视情况而定,可以忽略这个计算。
线上测量工具
web-vitals JavaScript library
实验室工具
原生的 JS API 测量
let cls=0;
new PerformanceObserver((entryList)=>{
for (const entry of entryList.getEntries()){
if (!entry.hadRecentInput){
cls +=entry.value;
console.log('Current CLS value:', cls, entry);
}
}
}).observe({type: 'layout-shift', buffered: true});
我们看一下结果是怎样的:
Google 官方 web-vitals 库
Google 官方也提供了一个web-vitals库,底层还是使用这个 API,只是帮我们处理了一些需要测量和不需测量的场景、以及一些细节问题。
我们可以根据这些原则来避免非预期布局移动:
更详细的内容可以看这里。
Google 开发的所有工具都支持 Core Web Vitals 的测量。工具如下:
这些工具对 Core Web Vitals 的支持如下:
打开 F12,就可以看到 Lighthouse,点击 Generate Report,即可生成报告。当然也可以添加 chrome 插件使用。
Lighthhouse 是一个实验室工具,本地模拟移动端和 PC 端对这几个方面进行测试。同时 lighthouse 还会针对这几个方面提出建议,在产品上线前值得一测。
Lighthouse 还提供了Lighthouse CI,把 Lighthouse 集成到 CI 流水线中。举个例子,每次在上线之前,跑 50 次流水线对 Lighthouse 的各项指标进行测试取平均值,一旦发现异常,立刻进行排查。把性能问题排查提前到发布之前。这块后面会细讲。
PageSpeed Insights(PSI)是一个可以分析线上和实验室数据的工具。它是根据线上环境用户真实的数据(在 Chrome UX 报告中)和 Lighthouse 结合出一份报告。和 Lighthouse 类似,它也会给出一些分析建议,可以知道页面的 Core Web Vitals 是否达标。
PageSpeed 只是提供对单个页面的性能测试,而 Search Console 是正对整个网站的性能测试。
PageSpeed Insights 也提供了API供我们使用。同样的,我们也可以把它集成到 CI 中。
Chrome UX Report (CrUX)是指汇聚了成千上万条用户体验数据的数据报告集,它是经过用户同意才进行上报的,目前存储在 Google BigQuery 上,可以使用账号登陆进行查询。它测量了所有的 Core Web Vitals 指标。
上面提到的 PageSpeed Insights 工具就是结合 CrUX 的数据进行分析给出的结论。
当然 CrUX 现在也提供了 API 共我们进行查询,可以查询的数据包括:
原理如下:
通过 API 的查询的数据每日都更新,并汇集了过去 28 天的数据。
具体的使用方式可以参考官方给出的demo。
Performance 是我们最常用的本地性能分析工具。
这里像提几点可以关注下的功能:Frame、Timings、Main、Layers、FPS。下面一一讲解。
点击 Frame 展开后,会看到有一个一个红色或绿色小块,这些代表着每帧的消耗时间。目前大多数设备的屏幕刷新率为 60 次/秒,浏览器渲染页面的每一帧的速率如果与设备屏幕的刷新率保持一致,即 60fps 时,我们是不会感知到页面卡的情况的。
我们把鼠标移上去看看:
这种是体验顺畅的情况。
再比如:
提示这一帧耗时了 30.9ms,当前是 32fps 并且是掉帧状态。
这里可以看到几个关键指标的时间点。
FP:First Paint;
FCP:First Contentful Paint;
LCP:Largest Contenful Paint;
DCL:DOMContentLoaded Event
L:OnLoad Event。
Main 是 DevTools 中最常用也是最重要的功能。
通过 record,我们可以查看页面上所有操作在主线程中的执行过程。也就是我们常说的流程:
一旦有任何一个流程时间过长或频繁发生,比如 Update Layer Tree 时间过长、频繁出现 RecalcStyles、Layout(重绘回流),那么需要引起注意。后面会举一个例子。
Layers 是浏览器在绘制过程中生成的一个层。因为浏览器底层渲染的本质是纵向分层、横向分块。这一块的知识点是发生在 Renderer Process 进程中。后面会以一个例子展开讲。
这里想提 Layers 的原因是,Layer 的渲染也会影响性能问题,而且有时候还不容易被发现!
Layers 面板一般不会默认展示出来,点击更多->more tools->Layers 即可打开。
点击 Layers 面板,点击左边下三角展开按钮,可以看见页面最终生成的合成层。右边左上角可以选择不同纬度进行查看。
选中某个层,可以查看该层生成的原因。
Chrome 的 Blink 内核给出了 54 种会生成合成层的原因:
constexpr CompositingReasonStringMap kCompositingReasonsStringMap[]={
{CompositingReason::k3DTransform, "transform3D", "Has a 3d transform"},
{CompositingReason::kTrivial3DTransform, "trivialTransform3D",
"Has a trivial 3d transform"},
{CompositingReason::kVideo, "video", "Is an accelerated video"},
{CompositingReason::kCanvas, "canvas",
"Is an accelerated canvas, or is a display list backed canvas that was "
"promoted to a layer based on a performance heuristic."},
{CompositingReason::kPlugin, "plugin", "Is an accelerated plugin"},
{CompositingReason::kIFrame, "iFrame", "Is an accelerated iFrame"},
{CompositingReason::kSVGRoot, "SVGRoot", "Is an accelerated SVG root"},
{CompositingReason::kBackfaceVisibilityHidden, "backfaceVisibilityHidden",
"Has backface-visibility: hidden"},
{CompositingReason::kActiveTransformAnimation, "activeTransformAnimation",
"Has an active accelerated transform animation or transition"},
{CompositingReason::kActiveOpacityAnimation, "activeOpacityAnimation",
"Has an active accelerated opacity animation or transition"},
{CompositingReason::kActiveFilterAnimation, "activeFilterAnimation",
"Has an active accelerated filter animation or transition"},
{CompositingReason::kActiveBackdropFilterAnimation,
"activeBackdropFilterAnimation",
"Has an active accelerated backdrop filter animation or transition"},
{CompositingReason::kXrOverlay, "xrOverlay",
"Is DOM overlay for WebXR immersive-ar mode"},
{CompositingReason::kScrollDependentPosition, "scrollDependentPosition",
"Is fixed or sticky position"},
{CompositingReason::kOverflowScrolling, "overflowScrolling",
"Is a scrollable overflow element"},
{CompositingReason::kOverflowScrollingParent, "overflowScrollingParent",
"Scroll parent is not an ancestor"},
{CompositingReason::kOutOfFlowClipping, "outOfFlowClipping",
"Has clipping ancestor"},
{CompositingReason::kVideoOverlay, "videoOverlay",
"Is overlay controls for video"},
{CompositingReason::kWillChangeTransform, "willChangeTransform",
"Has a will-change: transform compositing hint"},
{CompositingReason::kWillChangeOpacity, "willChangeOpacity",
"Has a will-change: opacity compositing hint"},
{CompositingReason::kWillChangeFilter, "willChangeFilter",
"Has a will-change: filter compositing hint"},
{CompositingReason::kWillChangeBackdropFilter, "willChangeBackdropFilter",
"Has a will-change: backdrop-filter compositing hint"},
{CompositingReason::kWillChangeOther, "willChangeOther",
"Has a will-change compositing hint other than transform and opacity"},
{CompositingReason::kBackdropFilter, "backdropFilter",
"Has a backdrop filter"},
{CompositingReason::kBackdropFilterMask, "backdropFilterMask",
"Is a mask for backdrop filter"},
{CompositingReason::kRootScroller, "rootScroller",
"Is the document.rootScroller"},
{CompositingReason::kAssumedOverlap, "assumedOverlap",
"Might overlap other composited content"},
{CompositingReason::kOverlap, "overlap",
"Overlaps other composited content"},
{CompositingReason::kNegativeZIndexChildren, "negativeZIndexChildren",
"Parent with composited negative z-index content"},
{CompositingReason::kSquashingDisallowed, "squashingDisallowed",
"Layer was separately composited because it could not be squashed."},
{CompositingReason::kOpacityWithCompositedDescendants,
"opacityWithCompositedDescendants",
"Has opacity that needs to be applied by compositor because of composited "
"descendants"},
{CompositingReason::kMaskWithCompositedDescendants,
"maskWithCompositedDescendants",
"Has a mask that needs to be known by compositor because of composited "
"descendants"},
{CompositingReason::kReflectionWithCompositedDescendants,
"reflectionWithCompositedDescendants",
"Has a reflection that needs to be known by compositor because of "
"composited descendants"},
{CompositingReason::kFilterWithCompositedDescendants,
"filterWithCompositedDescendants",
"Has a filter effect that needs to be known by compositor because of "
"composited descendants"},
{CompositingReason::kBlendingWithCompositedDescendants,
"blendingWithCompositedDescendants",
"Has a blending effect that needs to be known by compositor because of "
"composited descendants"},
{CompositingReason::kPerspectiveWith3DDescendants,
"perspectiveWith3DDescendants",
"Has a perspective transform that needs to be known by compositor because "
"of 3d descendants"},
{CompositingReason::kPreserve3DWith3DDescendants,
"preserve3DWith3DDescendants",
"Has a preserves-3d property that needs to be known by compositor because "
"of 3d descendants"},
{CompositingReason::kIsolateCompositedDescendants,
"isolateCompositedDescendants",
"Should isolate descendants to apply a blend effect"},
{CompositingReason::kFullscreenVideoWithCompositedDescendants,
"fullscreenVideoWithCompositedDescendants",
"Is a fullscreen video element with composited descendants"},
{CompositingReason::kRoot, "root", "Is the root layer"},
{CompositingReason::kLayerForHorizontalScrollbar,
"layerForHorizontalScrollbar",
"Secondary layer, the horizontal scrollbar layer"},
{CompositingReason::kLayerForVerticalScrollbar, "layerForVerticalScrollbar",
"Secondary layer, the vertical scrollbar layer"},
{CompositingReason::kLayerForScrollCorner, "layerForScrollCorner",
"Secondary layer, the scroll corner layer"},
{CompositingReason::kLayerForScrollingContents, "layerForScrollingContents",
"Secondary layer, to house contents that can be scrolled"},
{CompositingReason::kLayerForSquashingContents, "layerForSquashingContents",
"Secondary layer, home for a group of squashable content"},
{CompositingReason::kLayerForForeground, "layerForForeground",
"Secondary layer, to contain any normal flow and positive z-index "
"contents on top of a negative z-index layer"},
{CompositingReason::kLayerForMask, "layerForMask",
"Secondary layer, to contain the mask contents"},
{CompositingReason::kLayerForDecoration, "layerForDecoration",
"Layer painted on top of other layers as decoration"},
{CompositingReason::kLayerForOther, "layerForOther",
"Layer for link highlight, frame overlay, etc."},
{CompositingReason::kBackfaceInvisibility3DAncestor,
"BackfaceInvisibility3DAncestor",
"Ancestor in same 3D rendering context has a hidden backface"},
};
Rendering 面板也隐藏了很多好用的功能。
勾选了 Paint flashing 后,我们就会看到页面上有哪些内容被重绘了:
勾选了 Layout Shift Regions 后,进行交互,就可以看到哪些元素进行了布局移动:
这个工具有一个小插曲。
Frame Rendering Stats 的前身是 FPS meter,在 Google 版本85.0.4181.0改成了 Frame Rendering Stats,但是迫于用户抱怨,在 90 版本的时候又改回来了。
Frame Rendering Stats 主要显示不掉帧率。而 FPS 侧重于显示每秒的刷新率 fps。
Chrome 为什么要改成不掉帧率,是因为认为不掉帧率更能反映页面的顺畅度。而 FPS 显示每一秒渲染的帧数虽然能一定程度反映页面顺畅度,但是在一些特殊情况例如没有激活或空闲的页面,fps 会比较低,这样并不能反映真实情况。
(图片引自blink dev 论坛讨论)
在大型项目中,内存问题也是有发生。DevTools 也提供了内存分析工具供我们使用。
点击 Memory 面板,点击录制按钮。
点击录制后,会看到当前状态下内存的占用情况,根据大小排序,我们可以定位到内存占用过多的地方。
Google Search Console 其实就是监控和维护网站在 Google 搜索结果中的展示情况以及排查问题的平台。数据来源是 CrUX。
它会展示 3 个 Core Web Vitals metrics: LCP, FID, CLS。如果发现有问题,可以配合 PageSpeed 一起使用,分析问题。
(图片引自 vital-tools)
web.dev/measure是 google 官方提供的测量性能工具,也会提供类似 PageSpeed Insight 的指标,还会提供一些具体代码更改建议。
Google 也提供了扩展工具去测量 Core Web Vitals。可以从Store中进行安装。
当我们了解了这么多工具之后,琳琅满目,我们该如何选择?如何使用好这些工具进行分析?
最后一个章节想来谈谈监控。
我们在做性能优化的时候,常常会通过各种线上打点,来收集用户数据,进行性能分析。没错,这是一种监控手段,更精确的说,这是一种"事后"监控手段。
"事后"监控固然重要,但我们也应该考虑"事前"监控,否则,每次发布一个需求后,去线上看数据。咦,发现数据下降了,然后我们去查代码,去查数据,去查原因。这样性能优化的同学永远处于"追赶者"的角色,永远跟在屁股后面查问题。
举个例子,我们可以这样去做"事前"监控。
建立流水线机制。流水线上如何做呢?
我们来回顾一下前面的内容:
第一部分,讲了浏览器整体架构和渲染相关进程.为什么把这个章节也放到这篇性能优化的文章中?浏览器对于我们前端开发来说,是一个 sandbox 或者 darkbox。我们知道 js、html、css 结合起来就能实现我们的需求,但如果知道它是如何去渲染、执行、处理我们的代码,不管是对做需求还是性能优化,都能更知其然和所以然。
第二部分,雅虎军规是多年前提出的非常经典的优化建议。至今对于我们异常有很强的指导作用。你会发现它是从页面加载、页面渲染、到页面交互全面的一个指导建议。与今天 Chrome 和 W3c 提出的 Web Vitals 思路依然类似。
第三部分,性能指标。参考标准与业内标杆的建议,能更好地指导我们进行优化。
第四部分,性能工具。工欲善其事,必先利其器。这个道理大家都懂,运用好工具,才能让我们更加事半功倍。
第五部分,监控在性能优化中占很重要的部分,"事前"监控更重要,防患于未然。让性能优化成为一个预防者而不是追赶者。
罗里吧嗦说了很多,当然还有很多性能优化的细节没有讲到,如果有错误的地方欢迎指正。或者有什么好方法好建议也强烈欢迎私聊交流一下。
参考文章:
更多干货尽在腾讯技术,欢迎关注官方公众号:腾讯技术工程,交流群已建立,交流讨论可加QQ 群:160315980(备注腾讯技术) 。