浏览器原理与前端性能优化

923 阅读7分钟

1、浏览器架构(chrome的多进程架构)

  • Browser 进程:打开浏览器后,始终只有一个。该进程有 UI 线程、Network 线程、Storage 线程等。用户输入 url 后,首先是 Browser 进程进行响应和请求服务器获取数据。然后传递给 Renderer 进程。
  • Renderer 进程:每一个 tab 一个,负责 html、css、js 执行的整个过程。前端性能优化也与这个进程有关
  • Plugin 进程:与浏览器插件相关,例如 flash 等。
  • GPU 进程:浏览器共用一个。主要负责把 Renderer 进程中绘制好的 tile 位图作为纹理上传到 GPU,并调用 GPU 相关方法把纹理 draw 到屏幕上。

浏览器每个tab跑在不同的进程中,如果其中一个tab挂了,不会影响其他的tab。多进程会浪费内存,每个进程相同的基础架构不能共用,所以chrome会限制启动的进程数量。当打开的tab页过多时,会把同一个网站的tab放在同一个进程中。

2、页面渲染相关进程

2.1 Render Process

Renderer 进程:包括 3 个线程。合成线程(Compositor Thread)、主线程(Main Thread)、Compositor Tile Worker。
  • Compositor Thread:首先接收 vsync 信号(vsync 信号是指操作系统指示浏览器去绘制新的帧),任何事件都会先到达 Compositor 线程。如果主线程没有绑定事件,那么 Compositor 线程将避免进入主线程,并尝试将输入转换为屏幕上的移动。它将更新的图层位置信息作为帧通过 GPU 线程传递给 GPU 进行绘制。
  • Main Thread:主线程就是我们前端工程师熟知的线程,这里会执行解析 Html、样式计算、布局、绘制、合成等动作。所以关于性能的问题,都发生在了这里。所以应该重点关注这里
  • Compositor Tile Worker:由合成线程产生一个或多个 worker 来处理光栅化的工作。
主线程:

Recal Styles 和 Layout 指向了 requestAnimationFrame,这意味着有 Forced Synchronous Layout (or Styles)(强制回流和重绘)发生。

图片

图片

主线程的执行顺序如上图,但是如果只需要Paint,那么就不会有前面的步骤了。

  • Parse HTML: 解析HTML,使之变成DOM(Document Object Model);在解析HTML过程中会找到子资源(图像,CSS,JS等 ),浏览器的预加载程序会把这些请求发送到network线程中进行资源加载。如果遇到<script>标签,会停止HTML解析,因为JS会影响DOM的结构。
  • Recalc Style: 遇到CSS资源会加载CSS代码,来确定每个DOM节点的计算样式。即使没有样式,浏览器也会提供默认的样式。
  • Layout: 找到所有元素的几何关系,生成布局树(Render Tree)。
  • Paint:遍历布局树生成绘画记录,记录元素绘制的先后顺序。
  • Composite: 将页面分为若干层,分别进行光栅化,在根据用户滚动的页面来按需合成新的帧来展示效果。

浏览器工作大致流程

浏览器工作大致流程

主流浏览器的屏幕刷新率是60Hz,所以渲染一帧的时间必须保证在16ms以内才能不掉帧。

正常的情况下是:

执行 JS -> 空闲 -> 绘制(16ms)-> 执行 JS -> 空闲 -> 绘制(32ms)-> ...

掉帧的情况下:

执行很多 JS...(20ms)-> 空闲 -> 绘制(32ms)-> ...

这里执行的JS可以看成是Recal Styles 和 Layout 到 requestAnimationFrame过程,进行了很多次,造成时间的占用。所以让主线程执行的过程尽量少能达到性能优化的效果。减少页面的重绘,避免使用会让页面重绘的CSS。

2.2 GPU Process

GPU 进程:只有 GPU 线程,负责接收从 Renderer 进程中的 Compositor Thread 传过来的纹理,显示到屏幕上。

3、经典回顾

图片

图片

4、性能指标

4.1 Google 和 W3C 性能工作组提供的几种性能指标:

  • First contentful paint (FCP): 测量页面开始加载到某一块内容显示在页面上的时间。
  • Largest contentful paint (LCP): 测量页面开始加载到最大文本块内容或图片显示在页面中的时间。
  • First input delay (FID): 测量用户首次与网站进行交互(例如点击一个链接、按钮、js 自定义控件)到浏览器真正进行响应的时间。
  • Time to Interactive (TTI): 测量从页面加载到可视化呈现、页面初始化脚本已经加载,并且可以可靠地快速响应用户的时间。
  • Total blocking time (TBT): 测量从 FCP 到 TTI 之间的时间,这个时间内主线程被阻塞无法响应用户输入。
  • Cumulative layout shift (CLS): 测量从页面开始加载到状态变为隐藏过程中,发生不可预期的 layout shifts 的累积分数。

4.2 核心指标

图片

图片

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);
4.2.1 PerformanceObserver 性能观察者

PerformanceObserver是浏览器内部对Performance实现的观察者模式,也是现代浏览器支持的几个 Observer 之一。

  • 避免不知道性能事件啥时候会发生,需要重复轮训timeline获取记录。
  • 避免产生重复的逻辑去获取不同的性能数据指标
  • 避免其他资源需要操作浏览器性能缓冲区时产生竞态关系。

PerformanceObserver使用:

  1. 创建观察者
  2. 定义回调函数事件
  3. 定义要观察的目标对象
let observer = new PerformanceObserver(callback); 

const callback = (list, observer) => {
   const entries = list.getEntries();
   entries.forEach((entry) => {
    console.log('Name:'+entry.name+', Type: '+entry.entryType +', Start: '+entry.startTime+', Duration: '+entry.duration+'\n'); });
}

observer.observe({entryTypes: ["entryTypes"]});

其中callback中的list都是一个完整的PerformanceObserverEntryList对象:

4.2.2 LCP

Largest contentful paint (LCP):最大内容块显示的时间。直接使用JS API测量,除了IE大部分浏览器都支持。

//打开对应页面,在控制台输入
new PerformanceObserver((entryList) => {
 for (const entry of entryList.getEntries()) {
  console.log('LCP candidate:', entry.startTime, entry);
 }
}).observe({type'largest-contentful-paint'bufferedtrue});

image-20210715190858508

image-20210715190858508

优化点:

  • 减少大文件的加载;
  • 减少服务端响应时间;
4.2.3 FID

First Input Delay(FID),指从浏览器接收到了用户输入,到浏览器对用户的输入进行响应的延迟时间。当 FID 的时间为 100ms 或以内,则为 Good。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});

优化点:

减少主线程的工作,因为FID主要是主线程繁忙引起的用户响应不及时。

4.2.3 GLS

Cumulative Layout Shift,意外布局移动, 浏览器会监控两桢之间发生移动的不稳定元素。

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'bufferedtrue});

优化点:

减少页面的重绘

4.3 性能工具

4.3.1 Lighthouse

打开 F12,就可以看到 Lighthouse,点击 Generate Report,即可生成报告。当然也可以添加 chrome 插件使用。

image-20210719143708018

image-20210719143708018

4.3.2 PageSpeed Insights

一个可以分析线上和实验室数据的工具。它是根据线上环境用户真实的数据(在 Chrome UX 报告中)和 Lighthouse 结合出一份报告。和 Lighthouse 类似,它也会给出一些分析建议,可以知道页面的 Core Web Vitals 是否达标。

developers.google.com/speed/pages…

4.3.3 CrUX

Chrome UX Report (CrUX)是指汇聚了成千上万条用户体验数据的数据报告集,它是经过用户同意才进行上报的,目前存储在 Google BigQuery 上,可以使用账号登陆进行查询。它测量了所有的 Core Web Vitals 指标。

4.3.4 Performance

Performance提供几个重要的功能包括:Frame、Timings、Main、Layers、FPS。

Frame:

可以看到每帧的时间,重点关注红色的块,鼠标悬浮可以看到时间和帧率。以下为掉帧的情况。

image-20210719152247167

image-20210719152247167

Time:

包括几个指标,DOMContentLoaded Event,First Paint,First Contentful Paint,Largest Contentful Paint,Onload Event。

image-20210719154438322

image-20210719154438322

Main:

通过Main可以看到主线程的执行过程,避免出现过多的RecalcStyles、Layout(重绘回流)。

Layers:

页面渲染中的生成的一个层,可以查看层的生成原因。

Rendering:

  • Paint flashing:查看哪些内容被重绘了;
  • Layout Shift Regions 后,进行交互,就可以看到哪些元素进行了布局移动;

Memory:

点击录制后,会看到当前状态下内存的占用情况,根据大小排序,我们可以定位到内存占用过多的地方。

参考:

mp.weixin.qq.com/s/RCJftzmhQ…

juejin.cn/post/684490…

zhuanlan.zhihu.com/p/99394757

tsejx.github.io/javascript-…

kmanong.top/kmn/qxw/for…

web.dev/user-centri…

微信公众号,更好的交流!

扫码_搜索联合传播样式-白色版.png