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使用:
- 创建观察者
- 定义回调函数事件
- 定义要观察的目标对象
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', buffered: true});

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', buffered: true});
优化点:
减少页面的重绘
4.3 性能工具
4.3.1 Lighthouse
打开 F12,就可以看到 Lighthouse,点击 Generate Report,即可生成报告。当然也可以添加 chrome 插件使用。
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
Time:
包括几个指标,DOMContentLoaded Event,First Paint,First Contentful Paint,Largest Contentful Paint,Onload Event。

image-20210719154438322
Main:
通过Main可以看到主线程的执行过程,避免出现过多的RecalcStyles、Layout(重绘回流)。
Layers:
页面渲染中的生成的一个层,可以查看层的生成原因。
Rendering:
- Paint flashing:查看哪些内容被重绘了;
- Layout Shift Regions 后,进行交互,就可以看到哪些元素进行了布局移动;
Memory:
点击录制后,会看到当前状态下内存的占用情况,根据大小排序,我们可以定位到内存占用过多的地方。
参考:
微信公众号,更好的交流!