1.网络面板
包含:控制器、过滤器、抓图信息、时间线、详细列表,下载信息概
各页签介绍
控制器
- 抓包
- 搜索
- 清缓存
- 网络模拟
过滤器
过滤内容
抓图信息
- 渲染每一帧的图片
- 通过判断白屏的多少分析是否卡顿异常
时间线
HTTP、HTTPS、WebSocket 加载的状态和时间的一个关系
下载信息概要
- DOMContentLoaded,这个事件发生后,说明页面已经构建好 DOM 了,这意味着构建 DOM 所需要的 HTML 文件、JavaScript 文件、CSS 文件都已经下载完成了。
- Load,说明浏览器已经加载了所有的资源(图像、样式表等)。
详细列表
最核心模块,所以关键数据都在这里
列表属性
列表详情
单个资源的详情
鼠标移动到 具体行的waterfall选择,会自动弹出显示
resource scheduling
- Queuing : 每个域名最多维护 6 个 TCP 正在连接,超过需要等待,这里表示等了多久
connection start
- Stalled : 发起连接对TCP连接的检测消耗的时间
- DNS Lookup dns查找
- Initial connection : 与服务端建立tcp连接
- SSL :ssl连接
request /response
- Request sent : 发送数据给服务端,速度超快,不到1毫秒
- Waiting for server(TTFB) :等待接收服务器第一个字节的数据的时间点
- Content Download 阶段 :第一字节时间到接收到全部响应数据所用的时间
网络请求优化方案
1.Queuing过久
当首屏请求的css js 资源过多,会进入Stalled状态
- 使用多域名,解决单域名6个限制
- 使用http2,由于多路复用解决的限制问题
2.Waiting for server(TTFB)过久
也叫 TTFB 第一字节时间
原因
- 服务端后台处理逻辑过于复杂,数据库读写慢
- 网络卡顿
- 请求头信息过多,多余的cookie
解决
- 服务端增加缓存的方案,减少磁盘读写和cpu的运算
- 可以通过cdn复用资源,提高加载速度,内容压缩等
- 减少query 参数与 不必要的cookie传递
3.Content Download 过久
减少文件大小,使用内容压缩,删除多余注释空格,gzip
2.JS dom加载优化
DOM 是表述 HTML 的内部数据结构,它会将 Web 页面和 JavaScript 脚本连接起来,并过滤一些不安全的内容。
html解析流程
- 网络进程接收到响应头之后,判断content-type 的值是“text/html”
- 创建一个渲染进程
- 渲染进程准备好之后,网络进程和渲染进程之间会建立一个共享数据的管道
- 网络进程不断读取html 并 不断发送给渲染进程,渲染进程一直不断的实时解析html
dom解析器实现
主流程 : html文本字节流-> 分词器 -> 生成节点 -> dom
- html文本字节流 转化为 分词列表,为不同的tag定义不同的 StartTag 和 EndTag
- 通过 token 栈,不断的压入和推出同步输出树形的dom 结构(抽象语法树)
流程
<html>
<body>
<h1>hello</h1>
<div>jason</div>
</body>
</html>
- 每压入一个tag start 标签都会创建一个dom,这里压入了 html, body, h1
- 当当压入 h1 end 会判断上一个是否为h1 start 满足则弹出h1 的start和end
-
继续重复压入 div
-
最终全部退出 ,包含 html 和 body都找到匹配的end
js加载阻塞dom
- 当包含js的内联节点时,会暂停dom解析,优先执行js
- 当js通过引入,一样会暂停dom的解析,去发起网络请求并等待网络请求结束后再执行js
js加载阻塞dom优化方案
- chrome已做优化,预解析优化,当读取html文本时候,会启动预解析线程,进行提前下载js
- 可以使用async,defer进行异步加载
- async 一旦加载完成,会立即执行
- defer 需要在 DOMContentLoaded 事件之后
3.css dom的加载优化
cssom解析实现
document.styleSheets
样式计算 计算布局
css加载阻塞js执行
由于js不知道代码里是否操作了css,所以默认要处理完前面所有css的下载,才能执行js
css优化方案
- chrome已做优化,预解析优化,当读取html文本时候,会启动预解析线程,进行提前下载css
- 对于大的 CSS 文件,可以通过媒体查询属性,将其拆分为多个不同用途的 CSS 文件,这样只有在特定的场景下才会加载特定的 CSS 文件。
- 通过内联 JavaScript、内联 CSS 来移除这两种类型的文件下载,这样获取到 HTML 文件之后就可以直接开始渲染流程了。
- 但并不是所有的场合都适合内联,那么还可以尽量减少文件大小,比如通过 webpack 等工具移除一些不必要的注释,并压缩 JavaScript 文件。
4.分层和合成机制
概念
- 显示器显示图像的原理
- 60HZ,每秒固定读取 60 次前缓冲区中的图像,并显示到显示器上
- 显卡与显示器交互
- 显卡负责:合成新的图像,并将图像保存到后缓冲区中,当显卡把合成的图像写到后缓冲区,系统就会让后缓冲区和前缓冲区互换,这样就能保证显示器能读取到最新显卡合成的图像。一般显卡也是60HZ
- 一帧:渲染流水线生成的每一副图片称为一帧
- 帧率 :每1秒更新60帧,60hz 或60fps,才叫流畅
页面卡顿来源
生成帧的速度过慢,达不到60Hz
重排、重绘和合成
- 重排
- 会让整个渲染流水线的每个阶段都执行一遍
- 重绘
- 绘因为没有了重新布局的阶段,操作效率稍微高点,但是依然需要重新计算绘制信息,并触发绘制操作之后的一系列操作
- 合成
- 基于GPU
- 分层+合成
合成原理
- 那么绘制过程会生成|Paint BackGroundColor:Black | Paint Circle|这样的绘制指令列表
- 合成操作是在合成线程上完成的,这也就意味着在执行合成操作时,是不会影响到主线程执行的
分块
- 合成线程会将每个图层分割为大小固定的图块,然后优先绘制靠近视口的图块
- 纹理上传
- 计算机内存上传到 GPU 内存的操作会比较慢
- 解决方案:chrome在首次合成图块的时候使用一个低分辨率的图片
优化方案
通过will-change: transform告诉浏览器,这个单独实现合成进程渲染,不影响主线程
box {will-change: transform, opacity;}
系统地优化页面性能
页面优化定义
让页面更快地显示和响应
页面的3个阶段
- 加载阶段,是指从发出请求到渲染出完整页面的过程,影响到这个阶段的主要因素有网络和 JavaScript 脚本。交互阶段,主要是从页面加载完成到用户交互的整合过程,影响到这个阶段的主要因素是 JavaScript 脚本。关闭阶段,主要是用户发出关2. 闭指令后页面所做的一些清理操作。
- 关闭阶段,主要是用户发出关闭指令后页面所做的一些清理操作。
加载阶段优化方案
优化关键资源
- 第一个是关键资源个数
- 第二个是关键资源大小
- 第三个是请求关键资源需要多少个 RTT(Round Trip Time)
RTT
- 定义:RTT 就是这里的往返时延。
- 通常 1 个 HTTP 的数据包在 14KB 左右,所以 1 个 0.1M 的页面就需要拆分成 8 个包来传输了,也就是说需要 8 个 RTT。
优化流程计算分析
- html资源 :尽量小于14kb
- js 和 css资源: 由于渲染引擎有一个预解析的线程,都是并发执行,计算体积最大的即可
优化策略
- 如何减少关键资源的个数?
- 一种方式是可以将 JavaScript 和 CSS 改成内联的形式,比如上图的 JavaScript 和 CSS,若都改成内联模式,那么关键资源的个数就由 3 个减少到了 1 个。
- 另一种方式,如果 JavaScript 代码没有 DOM 或者 CSSOM 的操作,则可以改成 sync 或者 defer 属性;同样对于 CSS,如果不是在构建页面之前加载的,则可以添加媒体取消阻止显现的标志。当 JavaScript 标签加上了 sync 或者 defer、CSSlink 属性之前加上了取消阻止显现的标志后,它们就变成了非关键资源了
- 如何减少关键资源的大小?
- 可以压缩 CSS 和 JavaScript 资源,移除 HTML、CSS、JavaScript 文件中一些注释内容,也可以通过前面讲的取消 CSS 或者 JavaScript 中关键资源的方式。
- 如何减少关键资源 RTT 的次数?
- 可以通过减少关键资源的个数和减少关键资源的大小搭配来实现。除此之外,还可以使用 CDN 来减少每次 RTT 时长。 在优化实际的页面加载速度时,你可以先画出优化之前关键资源的图表,然后按照上面优化关键资源的原则去优化,优化完成之后再画出优化之后的关键资源图表。
交互阶段
一个大的原则就是让单个帧的生成速度变快。
1. 减少 JavaScript 脚本执行时间
- 一种是将一次执行的函数分解为多个任务
- 使得每次的执行时间不要过久。
- 另一种是采用 Web Workers
- 你可以把 Web Workers 当作主线程之外的一个线程,在 Web Workers 中是可以执行 JavaScript 脚本的,不过 Web Workers 中没有 DOM、CSSOM 环境,这意味着在 Web Workers 中是无法通过 JavaScript 来访问 DOM 的,所以我们可以把一些和 DOM 操作无关且耗时的任务放到 Web Workers 中去执行。
2. 避免强制同步布局
1.正常情况下的布局操作
都是在另外的任务中异步完成的
<html>
<body>
<div id="main_div">
<li id="time_li">xxxx</li>
<li>yyy</li>
</div>
<p id="demo"> 强制布局 demo</p>
<button onclick="foo()"> 添加新元素 </button>
<script>
function foo() {
let main_div = document.getElementById("main_div")
let new_node = document.createElement("li")
let textnode = document.createTextNode("mynode")
new_node.appendChild(textnode);
document.getElementById("main_div").appendChild(new_node);
}
</script>
</body>
</html>
2.强制同步布局
JavaScript 强制将计算样式和布局操作提前到当前的任务中
function foo() {
let main_div = document.getElementById("main_div")
let new_node = document.createElement("li")
let textnode = document.createTextNode("mynode")
new_node.appendChild(textnode);
document.getElementById("main_div").appendChild(new_node);
// 由于要获取到 offsetHeight,
// 但是此时的 offsetHeight 还是老的数据,
// 所以需要立即执行布局操作
console.log(main_div.offsetHeight)
}
优化代码
function foo() {
let main_div = document.getElementById("main_div")
// 为了避免强制同步布局,在修改 DOM 之前查询相关值
console.log(main_div.offsetHeight)
let new_node = document.createElement("li")
let textnode = document.createTextNode("mynode")
new_node.appendChild(textnode);
document.getElementById("main_div").appendChild(new_node);
}
3. 避免布局抖动
是指在一次 JavaScript 执行过程中,多次执行强制布局和抖动操作。
function foo() {
let time_li = document.getElementById("time_li")
for (let i = 0; i < 100; i++) {
let main_div = document.getElementById("main_div")
let new_node = document.createElement("li")
let textnode = document.createTextNode("mynode")
new_node.appendChild(textnode);
new_node.offsetHeight = time_li.offsetHeight;
document.getElementById("main_div").appendChild(new_node);
}
}
4. 合理利用 CSS 合成动画
will-change,这是告诉渲染引擎需要将该元素单独生成一个图层。
5. 避免频繁的垃圾回收
频繁创建临时对象,那么垃圾回收器也会频繁地去执行垃圾回收策略。
5.虚拟DOM和实际的DOM有何不同?
操作dom的缺点
- 会触发重排
- 频繁dom操作甚至会导致强制同步布局和布局抖动
什么是虚拟dom
- 改变内容都在虚拟dom上
- 当内容变化时,只是对比调整虚拟dom的内容
- 收集完后,再一次性提交,减少重复操作的损耗
虚拟dom运行阶段
- 创建阶段
- 新老虚拟dom节点和真实dom一样,一次性创建首次显示的所有dom内容。
- 更新阶段
- 通过老的虚拟dom和新的虚拟dom比较,只针对变化的内容,做dom操作
- react的 diff算法
- Stack reconciler:老算法reconciliation
- Fiber reconciler: 新算法,执行算法的过程中出让主线程,这样就解决了 Stack reconciler 函数占用时间过久的问题
双缓存
- 正常前端显示内容是通过dom输出
- 通过虚拟dom多加一层
- 在游戏开发中,视图显示通过直接读取前缓冲区显示。
- 通过多加一层缓存区2,把复杂的运算都在缓存区2完成,再讲缓存2同步到缓冲区上
- 能解决页面无效刷新和闪屏的问题
MVC架构
- model -> controller > view
- 等价于 react 中的 state + vuex+ + view
- view就是 vm,处理所有视图显示
6.PWA
定义
Progressive Web App渐进式网页应用,它由很多技术组成的一个理念,其核心思想是渐进式。
web应用对比原生
缺点
- 缺少离线能力
- 缺少服务端主动推送给客户端能力
- 缺少程序统一入口
web work
- Web Worker 其实就是在渲染进程中开启的一个新线程,它的生命周期是和页面关联的
- 数据传输使用postmessage
service work
- 是页面和网络之间的拦截层
- 拦截数据
- 缓存数据
- 架构实现
- Service Worker 是运行在浏览器进程中的,生命周期最长
- 可以被多个页面访问数据
- 支持服务端推送
- 安全
- 必须采用 HTTPS 协议
- 同时支持 Web 页面默认的安全策略、储入同源策略、内容安全策略(CSP)等
manifest.json
程序一级入口
7.WebComponent
组件化的思想
- 对内高内聚,对外低耦合
- 对内各个元素彼此紧密结合、相互依赖,对外和其他组件的联系最少且接口简单。
前端组件化的难点
CSS 和 DOM 都是定义在全局上的,所以会互相影响
webcomponent
是一套技术的组合
- HTML templates(HTML 模板)
- Custom elements(自定义元素)
- Shadow DOM(影子 DOM)
操作流程
- 使用 template 属性来创建模板
- 模板元素是不会被渲染到页面上
- 我们需要创建一个 自定义的类
- 查找模板内容;创建影子 DOM;再将模板添加到影子 DOM 上。
- 使用 HTML 元素一样使用该元素
实现局部沙盒的效果
影子 DOM实现原理
特征
影子 DOM 中的元素对于整个网页是不可见的;影子 DOM 的 CSS 不会影响到整个网页的 CSSOM,影子 DOM 内部的 CSS 只对内部的元素起作用。
实现原理
-
每个影子 DOM 你都可以看成是一个独立的 DOM,它有自己的样式、自己的属性,内部样式不会影响到外部样式,外部样式也不会影响到内部样式。
-
当浏览器通过 DOM 接口去查找元素时,渲染引擎会去判断 自定义标签 属性下面的 shadow-root 元素是否是影子 DOM,如果是影子 DOM,那么就直接跳过 shadow-root 元素的查询操作。
-
当生成布局树的时候,渲染引擎也会判断 属性下面的 shadow-root 元素是否是影子 DOM,如果是,那么在影子 DOM 内部元素的节点选择 CSS 样式的时候,会直接使用影子 DOM 内部的 CSS 属性。
8.浏览上下文组
- 在同一域名下,通过标签打开或window.open打开同一站点(包括二级域名也可以)下的都属于 同一个浏览上下文组
- 共用一个进程
- 如果是新的tab页签再输入,就是独立的两个进程
- 当在标签加入 rel=noopener,则会断开同一浏览上下文组的关系,独立两个进程
- 站点隔离
- 在同一个页面下,内嵌不同的iframe ,也是根据同一站点原则来划分 浏览上下文组
9.requestAnimationFrame
单消息队列队头阻塞问题
通过鼠标触发的点击任务、滚动页面任务;
通过手势触发的页面缩放任务;
通过 CSS、JavaScript 等操作触发的动画特效等任务。
解决方案
- 对消息队列的类型做细化处理
- 输入事件的消息队列,用来存放输入事件。2.合成任务的消息队列,用来存放合成事件。3.默认消息队列,用来保存如资源加载的事件和定时器回调等事件。4.空闲消息队列,用来存放 V8 的垃圾自动垃圾回收这一类实时性不高的事件。
- 针对不同的类型,还要分不同的场景区分优先级
渲染不同步
显示器读取缓存图片时间,和浏览器生成的合成图时间不一致,很难确保在同一帧
后果
- 渲染进程生成的帧速比屏幕的刷新率慢,就会显示卡顿
- 渲染进程生成的帧速比屏幕的刷新率快,就会丢帧
- 不同的两套系统很难做到同步
解决方案
- 当显示器将一帧画面绘制完成后,并在准备读取下一帧之前,显示器会发出一个垂直同步信号(vertical synchronization)给 GPU,简称 VSync。
- 当渲染进程接收到用户交互的任务后,接下来大概率是要进行绘制合成操作,因此我们可以设置,当在执行用户交互的任务时,将合成任务的优先级调整到最高。接下来,处理完成 DOM,计算好布局和绘制,就需要将信息提交给合成线程来合成最终图片了,然后合成线程进入工作状态。
- 现在的场景是合成线程在工作了,那么我们就可以把下个合成任务的优先级调整为最低,并将页面解析、定时器等任务优先级提升。在合成完成之后,合成线程会提交给渲染主线程提交完成合成的消息.
- 如果当前合成操作执行的非常快,比如从用户发出消息到完成合成操作只花了 8 毫秒,因为 VSync 同步周期是 16.66(1/60)毫秒,那么这个 VSync 时钟周期内就不需要再次生成新的页面了。
- 那么从合成结束到下个 VSync 周期内,就进入了一个空闲时间阶段,那么就可以在这段空闲时间内执行一些不那么紧急的任务,比- 如 V8 的垃圾回收,或者通过 window.requestIdleCallback() 设置的回调任务等,都会在这段空闲时间内执行。
任务饿死
有些低优先级的任务可能从头到尾都没机会执行,chrome会设置了连续执行多个高优先级后,一定会执行一个低优先级
10.性能优化- lighthouse加载阶段
监测并分析 Web 性能 (Performance);
监测并分析 PWA(Progressive Web App) 程序的性能;监测并分析 Web 应用是否采用了最佳实践策略 (Best practices);监测并分析是否实施了无障碍功能 (Accessibility),无障碍功能让一些身体有障碍的人可以方便地浏览你的 Web 应用。监测并分析 Web 应用是否采实施了 SEO 搜素引擎优化 (SEO)。
可优化项 (Opportunities)
lighthouse生成指标
- 首次绘制 (First Paint) 网络原因加载慢
- 首次有效绘制 (First Meaningfull Paint);
- 等价于LCP,那么有可能是加载关键资源花的时间过久,也有可能是 JavaScript 执行过程中所花的时间过久
- 首屏时间 (Speed Index); 与上面FMP一致
首次 CPU 空闲时间 (First CPU Idle);
- 不需要等到页面上的所有元素都可交互,只要可以对大部分用户输入做出响应即可。要缩短首次 CPU 空闲时长,我们就需要尽可能快地加载完关键资源,尽可能快地渲染出来首屏内容,
- 完全可交互时间 (Time to Interactive);
- 页面的内容已经完全显示出来了,所有的 JavaScript 事件已经注册完成,页面能够对用户的交互做出快速响应,通常满足响应速度在 50 毫秒以内。如果要解决 TTI 时间过久的问题,我们可以推迟执行一些和生成页面无关的 JavaScript 工作。
- 最大估计输入延时 (Max Potential First Input Delay)。
- 你的 Web 页面在加载最繁忙的阶段, 窗口中响应用户输入所需的时间,为了改善该指标,我们可以使用 WebWorker 来执行一些计算,从而释放主线程。另一个有用的措施是重构 CSS 选择器,以确保它们执行较少的计算。
渲染进程的关键点
- FP 创建空白页面的这个时间点称为 First Paint
- FCP 当页面中绘制了第一个像素时,First Content Paint
- FMP 首次有效绘制 比较复杂,可忽略,用LCP代替,First Meaningfull Paint
- LCP 当首屏内容完全绘制完成时,Largest Content Paint
- DCL DOMContentLoad 事件触发
- L OnLoadEvent事件触发
11.性能优化- 加载阶段+交互performance
概览面板
- FPS(页面帧速)出现红色点,证明一帧渲染时间过长,会导致卡顿
- NET 过长,请求接口时间
- CPU图形占用过大,证明主线程执行时间过长,会影响其他任务执行
- HEAP内存曲线一直增高,没有降下来可能是内存泄露
- 鼠标滚动或 “WASD”四个键来可以调整选择范围
性能面板
- Main 渲染主线程的任务执行过程
- 渲染流水线任务
- JavaScript 执行
- V8 的垃圾回收
- 定时器设置的回调任务
- Compositor 合成线程的任务执行过程
- 合成线程中的Raster光栅化线程池,多条线程
- GPU : GPU 进程主线程的任务执行过程
- Frame显示的每一视图帧
- Network网络请求详情
- Timings 显示关键的FP、LCP、DOMContentLoaded、Onload节点
- 渲染进程中的Chrome_ChildIOThread - IO线程
- 用于收发不同进程中的消息
- Interactions 指标
- 用来记录用户交互操作,比如点击鼠标、输入文字等交互信息
详情面板
在性能面板点击对应的指标,详情会显示具体的信息
main详细分析
- 一个灰色task就是一个任务,里面嵌套的是一个个过程,橙色代表方法调用
- 蓝色标识解析html
- 紫色代表 样式计算
- 绿色块代表绘制
页面完成流程
导航阶段流程
- 页面执行pagehide、visibilitychange 和 unload 等事件
- Send request发送请求
- Receive Respone 过程接受到http响应头
- Recive Data已经接受到数据
- Finish load 表示网络请求已结束
解析html阶段
- 先执行页面dom事件
- readyStateChange、DOMContentLoaded
- 解析js,Evalute Script
- Evalute Script 脚本执行
- Complie Script编译脚本
- 程序执行,调用匿名函数anonymous
- 调用setNewArea,同时调用document.append 触发了 节点变化
- 再次触发ParserHTML
- 解析css,Reculate Style
- 生成cssom
合成生成位图阶段
- dom事件:readyStateChange、load、pageshow
- 执行布局 Layout
- 更新层树 Update LayerTree
- 准备绘制列表了Paint
- 根据绘制列表来生成相应图层Composite Layers
- 合成线程Compositor的对执行记录逐个处理
- 合成线程维护Raster 线程Rasterize Paint
- Rasterize 线程传输数据给 GPU 线程
- GPU 生成图像显示到显示器