前端性能指标&优化
参考资料从输入 URL 到页面展示到底出现了什么?Web 指标-Google关于 cdn、回源等问题一网打尽前端性能优化之 rel=“prefetch“预/懒加载功能
前置知识
从一个经典的面试题引出文案内容: 输入 url 到页面最后呈现都出现了什么? URL 解析判断输入是搜索关键字还是 url 位置,对 url 解析DNS 域名解析获取 IP 位置缓存查询:浏览器缓存(chrome://net-internals/#dns)->系统缓存(host)->路由器缓存->运营商缓存(IPS)->根域名服务器向本地 DNS 服务器发送查找报文"query zh.wikipedia.org"本地 DNS 服务器检测自己缓存,存在返回,不存在向根域名服务器发送查找报文"query zh.wikipedia.org",得到顶级域 .org 的顶级域名服务器位置DNS 服务器向 .org 域的顶级域名服务器发送查找报文"query zh.wikipedia.org",得到二级域 .wikipedia.org 的权威域名服务器位置DNS 服务器向 .wikipedia.org 域的权威域名服务器发送查找报文"query zh.wikipedia.org",得到主机 zh 的 A 记录,存入自己缓存并返回给客户端拓展方向:什么是 CDN?、CDN 回源?按照 IP 位置创立 TCP 链接(三次握手)三次挥手重点是为了双方确认过信息,能够创立起通信,见图 1抽象一点的话,这个过程能够想象成两个人分别站在山谷的两边,想要相互聊天,然则又不确定自己喊一嗓子之后对方到底能不可听到,而后就双方起始聊天之前先互相喊几嗓子,确认一下双方是不是都能够听到第1次:主机 A 发给 B 一个标识位 SYN,期盼经过该数据告诉主机 B 创立连接相当于 A 想找 B 聊天,就喊了一个 x 给 B第二次:主机 B 经过一个确认应答的 ACK 和同步序列号 SYN 响应给 A,这般主机 A 经过 ACK 就晓得主机 B 接收到了第1次的握手的数据,并且主机 A 经过响应的 SYN 晓得了要从那个序列号做标记相当于 B 听到了 A 喊的 x,晓得 A 想聊天,B不晓得A能不可听到自己说话,而后 B 就把 A 第1次的 x 和自己想说的话 y 一块喊给了 A第三次:主机 A 发送主机 B 响应的 SYN+1,这般主机 B 就晓得主机 A 收到了自己的信息,后面就能够传输数据了而后 A 收到了第1次喊话的 x+1,这般A就确认了B能够听到自己的话,而后 A 再把 B 说的话 y+1 喊给 B,这般B收到A的信息之后就晓得A能够听到自己的话,后面双方就能够愉快的聊天了发送 http 请求,服务器响应,缓存判断(强缓存|协商缓存)请求:发送命令+发送请求头信息+空白行+请求体(post)响应:响应状态 + 响应头+空白行+响应体强缓存:cache-control(max-age)、Expires协商缓存:返回 ETag、Last-modified 和请求 IF-none-match、IF-modified-since浏览器解析响应内容并渲染页面,见图 2解析 HTML,生成 DOM 树解析 CSS,生成 CSSOM(CSS Object Model)合并 DOM 树和 CSSOM,生成 Render 树而后交给 Layout 引擎,按照 Render 树信息,获取节点的元素体积和位置等布局信息而后交给 Paint 引擎,绘制页面像素信息,表示在屏幕上这个过程并不是一次性完成的,而是可能存在重复或交叉的状况,例如当遇到 JavaScript 或动态资源时,可能需要重新构建或更新某些部分。拓展方向:重排、重绘重排(reflow):元素的几何尺寸出现变化需要重新计算元素在页面中的位置和体积,这个过程会交给 Layout 引擎,引起整颗 Render 树重新计算,开销很强重绘(repaint):元素的外观(颜色、背景、边框)出现变化,浏览器需要重新绘制,这个过程触及到 Paint 引擎,直接绘制,执行效率比重排高连接结束关闭 TCP 链接(四次挥手)抽象层面理解的话,就好比男女伴侣分手第1次:男方想分手,就给女方发一个 FIN 包(暗示结束),并等待女方的回答第二次:女方收到了男方的 FIN 包,就给男方回复了一个 ACK 包(暗示确认),告诉他“我晓得你想分手了”,然则女方还有有些话想说,不会马上同意分手(暗示服务器需要时间处理关闭的规律)第三次:女方说完了自己的话(关闭规律处理结束),就给男方发一个 FIN 包(表结束),暗示自己亦同意分手了,并等待男方的回复第四次:男方收到了 FIN 包,就给女方回一个 ACK 包(表确认),暗示自己亦同意分手了,并且俩人再也不有话说。这般女方听到之后俩人就正式断开了链接
三次握手与四次挥手
浏览器解析响应内容并渲染页面
拓展方向
TCP 与 UDP 的区别?TCP:TCP 协议则是创立在IP协议之上的。TCP 协议负责在两台计算机之间创立可靠连接,保准数据包按次序到达。TCP 协议会经过三次握手创立连接,而后,对每一个 IP 包编号,保证对方按次序收到,倘若包丢掉了,就自动重发。许多常用的更高级的协议都是创立在 TCP 协议基本上的,例如用于浏览器的HTTP协议、发送邮件的SMTP协议等。一个 TCP 报文除了包括要传输的数据外,还包括源 IP 位置和目的 IP 位置,源端口和目的端口。UDP:UDP 则是面向无连接的协议,TCP 必须创立靠谱的连接运用 UDP 协议时,不需要创立连接,只需要晓得对方的 IP 位置和端口号,就能够直接发数据包。然则,能不可到达就不晓得了。因此 UDP 传输的数据不靠谱UDP 是面向报文的,无拥塞掌控,因此速度快,比较多媒介通讯,如:聊天和视频,支持一对1、一对多、多对1、多对多,如浏览器上的视频聊天然则此刻平常的直播服务,为了供给稳定的直播环境,都是采用的 TCP 链接HTTP1.0、1.1、2.0 的区别?1.0每次 tcp 连接只能发送一个请求,当服务器响应后就会关闭这次连接,下一个请求需要再次创立TCP 连接1.1默认采用连续链接(TCP 链接默认不关闭,能够被多个请求复用,不消声明 Connection:keep-alive)增多了管道机制,在同一个 TCP 连接里,浏览器准许多个请求同期发送,增多了并发性,进一步改善了 HTTP 协议的效率然则在同一个 TCP 连接里,所有的数据通信是按秩序进行的。倘若前面有一个请求回复慢,就会有许多请求排队,导致“队头堵塞”2.0加了双工模式,即:不仅客户端能够同期发送多个请求,服务端亦能同期处理多个请求,处理了队头堵塞的问题运用了多路复用的技术,做到同一个连接并发处理多个请求,况且并发请求数量比 http1.1 大了好几个数量级增加服务器推送的功能,不经请求,服务端主动向客户端发送数据
HTTP1.1 长连接和 2.0 多路复用的区别?长连接:同一时间一个 TCP 连接只能处理一个请求,采用一问一答的形式,上一个请求响应后才可处理下一个请求,因为浏览器有最大TCP连接的限制(6 个),因此有了最大并发请求数的限制。多路复用:同域名下所有的通信都在单个TCP连接上完成,消除了因多个 TCP 连接而带来的延时和内存消耗。单个连接上能够并行的请求和响应,之前互不干扰为何 HTTP1.1 不可实现多路复用?http2.0 是基于二进制"帧"的概念,http1.1 是基于“文本分割”解析的协议在 http1.1 的报文结构中,服务器需要持续的读入字节,直到遇到换行符(br),或说是空白行。处理次序是串行的,一个请求和一个响应需要经过一问一答的形式才可对应起来http2.0 中,有两个非常重要的概念,分别是“帧”和“流”帧:表率着最小的数据单位,每一个帧会标识出该帧属于哪个流,流亦便是帧构成的数据流多路复用:便是在每一个TCP连接中能够存在多条流。换句话说,亦便是能够发送多个请求,能够经过帧中的标识晓得属于哪个请求经过这个技术,能够避免 HTTP 就版本中的队头堵塞问题,极重的加强传输性能什么是 CDN?、CDN 回源?CDN 是内容分发网络的缩写,它是一种将互联网内容快速传输给用户的技术。CDN 经过在区别地区安排多个服务器,将内容缓存到离用户近期的服务器上,从而减少延迟和带宽消耗。CDN 回源指的是当 CDN 节点无缓存用户请求的内容时,需要向源站(原始服务器)请求资源,并重新设置缓存的过程。回源能够保准 CDN 节点总是能够供给最新和最完整的内容给用户,但亦会增多源站的负载和流量成本。JS 中的垃圾回收机制(GC)
垃圾回收是一站自动管理内存的机制,js 中的 gc 重点有两种算法:引用计数、标记清除 引用计数:比较早的算法记录每一个对象被引用的次数,当一个对象的引用次数为零时,就能够被释放缺点:没法处理循环引用的状况当两个或多个对象相互引用时,她们的引用计数永远不会为零 循环引用例子:vara = {};var b = {};
a.b = b;
b.a = a;标记清除:常用的算法,能够处理循环引用问题遍历所有的对象,标记哪些达到的对象,而后清除哪些不达到的对象这般即使出现循环引用,然则当她们都不达到了,就会被标记清除算法回收
标记清除处理循环引用的例子: var a = {};
varb = {};
a.b = b;
b.a = a;
a =null;
b = null;这儿,对象 a 和对象 b 互相引用,但在最后两行,它们都被赋值为 null,再也不被任何变量引用。 倘若运用引用计数算法,它们的引用计数仍然为 1,不会被回收。 但倘若运用标记清除算法,它会从全局对象 window)做为根起始遍历所有的对象,发掘 a 和 b 都不达到了(由于无任何变量指向它们),就会把它们标记为垃圾,并在下一次 gc 时清除它们。
检测工具
原理:便是在合适的机会,打上合适的时间戳,或暴露出事件。而后经过这些时间戳之间的差值,得出⼀个耗时时间。这个耗时时间就能够反映出咱们⻚⾯的关联性能。工具如下: API: window.performance性能监测对象:PerformanceObserver.observe()npm 包:web-vitals研发者工具:Lighthouse
1.Performance
Performance 接口能够获取到当前页面中与性能关联的信息。它是 High Resolution Time API 的一部分,同期亦融合了 Performance Timeline API、Navigation Timing API、 User Timing API和 Resource Timing API。 User Timing API :⽤户⾃⼰定义在代码中经过调⽤ performance.mark(key) ⽅法定义的时间点。Navigation Timing API : 资源请求的时间戳。Navigation Timing API :它⾥⾯包括的是咱们从请求起始,到全部⻚⾯的完全表示的各个周期的时间点,包 含了以下:
key 值
value 值解释
navigationStart
当前浏览器窗⼝的前⼀个⽹⻚关闭,发⽣ unload 事件时的时间戳。倘若无前⼀个⽹⻚,就等于 fetchStart(亦便是输⼊ URL 起始,第⼀步便是卸载上个⻚⾯)
redirectStart
第⼀次重定向起始时的时间戳,倘若无重定向,或上次重定向不是同源的,则为 0
redirectEnd
最后⼀次重定向完成,亦便是 Http 响应的最后⼀个字节返回时的时间戳,倘若无重定向,或上次重定向不是同源的,则为 0
fetchStart
浏览器准备经过 HTTP 请求去获取⻚⾯的时间戳。在检测应⽤缓存之前出现
domainLookupStart
域名查找起始时的时间戳。倘若使⽤持久连接,或从本地缓存获取信息的,等同于 fetchStart
domainLookupEnd
域名查找结束时的时间戳。倘若使⽤持久连接,或从本地缓存获取信息的,等同于 fetchStart
connectStart
HTTP 请求起始向服务器发送时的时间戳,倘若是持久连接,则等同于 fetchStart
connectEnd
浏览器与服务器之间的连接建⽴时的时间戳,连接建⽴指的是所有握⼿和认证过程所有结束
requestStart
浏览器向服务器发出 HTTP 请求时(或起始读取本地缓存时)的时间戳
responseEnd
浏览器从服务器收到(或从本地缓存读取)最后⼀个字节时(倘若这里之前 HTTP 连接已然关闭,则返回关闭时)的时间戳
domLoading
当前⽹⻚ DOM 结构起始解析时,亦便是 document.readyState 属性变为“loading”、并且相应的 readystatechange 事件触发时的时间戳
domInteractive
当前⽹⻚ DOM 结构结束解析
domContentLoadedEventStart
当前⽹⻚ DOMContentLoaded 事件发⽣时,亦便是 DOM 结构解析完毕、所有脚本起始运⾏时的时间戳
domContentLoadedEventEnd
当前⽹⻚ DOMContentLoaded 事件发⽣时,亦便是 DOM 结构解析完毕、所有脚本运⾏完成时的时间戳
domComplete
当前⽹⻚ DOM 结构⽣成时,亦便是 Document.readyState 属性变为“complete”
loadEventStart
当前⽹⻚ load 事件的回调函数起始时的时间戳。倘若该事件还无发⽣,返回 0
loadEventEnd
当前⽹⻚ load 事件的回调函数结束时的时间戳。倘若该事件还无发⽣,返回 0
2.performanceObserver
PerformanceObserver.observe() :指定监测的 entryTypes 的集合。
当 performance entry 被记录并且指的是定的 entryTypes之⼀的时候,性能观察者对象的回调函数会被调⽤。 var observer = new PerformanceObserver(callback);
// 直接往 PerformanceObserver() 入参匿名回调函数
// 成功 new 了一个 PerformanceObserver 类的,名为 observer 的对象
varobserver =new PerformanceObserver(function (list, obj) {
var entries = list.getEntries();
for (var i = 0; i < entries.length; i++) {
//处理“mark”和“frame”事件}
});//调用 observer 对象的 observe() 办法
observer.observe({ entryTypes: [mark, frame] });3.web-vitals
⽬前只能统计CLS | FCP | FID | LCP | TTFB 。倘若需要扩充的话,就能够使⽤上⾯的 Performance 进⾏更改 import { getCLS, getFID, getLCP } from web-vitals;
getCLS(console.log);
getFID(console.log);
getLCP(console.log);
// ...
4.Lighthouse
运用研发者工具中的LighthouseTab 或运用 Node CLI 的方式 npm install -g lighthouse
lighthouse <url>
性能指标
1.白屏时间 FP
输入 URL 起始,到页面起始有变化,只要有任意像素点的变化,就算是白屏时间完结 function getFP() {
new PerformanceObserver((entryList, observer) => {
let entries = entryList.getEntries();
for (let i = 0; i < entries.length; i++) {
if (entries[i].name === first-paint) {
console.log(FP, entries[i].startTime);
}
}
}).observe({ entryTypes: [paint] });
}2.首次内容绘制时间 FCP
指的是⻚⾯上绘制了第⼀个元素的时间
FP 与 FCP 的最⼤的区别就在于:FP 指的是绘制像素,⽐如说⻚⾯的背景⾊是灰⾊的,那样在表示灰⾊背景时就记录下了 FP 指标。然则此时 DOM 内容还没起始绘制,可能需要⽂件下载、解析等过程,仅有当 DOM 内容发⽣变化才会触发,⽐如说渲染出了⼀段⽂字,此时就会记录下 FCP 指标。因此呢说咱们能够把这两个指标认为是和⽩屏时间关联的指标,因此肯定是最快越好。 function getFCP() {
new PerformanceObserver((entryList, observer) => {
let entries = entryList.getEntries();
for (let i = 0; i < entries.length; i++) {if (entries[i].name === first-contentful-paint) {
console.log(FCP, entries[i].startTime);
}
}
}).observe({ entryTypes: [paint] });
}3.⾸⻚时间
当 onload 事件触发的时候,亦便是全部⾸⻚加载完成的时候 function getFirstPage() {
console.log(
FIRSTPAGE,
performance.timing.loadEventEnd - performance.timing.fetchStart,
);
}4.最⼤内容绘制 LCP
⽤于记录视窗内最⼤的元素绘制的时间,该时间会随着⻚⾯渲染变化⽽变化,由于⻚⾯中的最⼤元素在渲染过程中可能会发⽣改变,另一该指标会在⽤户第⼀次交互后停⽌记录。 function getLCP() {
new PerformanceObserver((entryList, observer) => {
let entries = entryList.getEntries();
const lastEntry = entries[entries.length - 1];
console.log(LCP, lastEntry.renderTime || lastEntry.loadTime);
}).observe({entryTypes: [largest-contentful-paint] });
}
5.⾸次可交互时间 TTI
FCP 指标后,首个长任务执行时间点,其后无长任务或 2 个 get 请求。 从 FCP 指标后起始计算连续 5 秒内⽆⻓任务(执⾏时间超过 50 ms)且⽆两个以上正在进⾏中的 GET 请求往前回溯⾄ 5 秒前的最后⼀个⻓任务结束的时间
function getTTI() {
let time= performance.timing.domInteractive - performance.timing.fetchStart;
console.log(TTI, time);
}6.⾸次输⼊延迟 FID从用户第1次与页面交互到浏览器实质能够起始处理事件的时间,在 FCP(首次内容绘制) 和 TTI (首次可交互时间)之间⽤户⾸次与⻚⾯交互时响应的延迟eg:点击输入框后,因渲染等导致的延迟function getFID() {
new PerformanceObserver((entryList, observer) => {
let firstInput = entryList.getEntries()[0];
if (firstInput) {
constFID = firstInput.processingStart - firstInput.startTime;console.log(FID, FID);
}
}).observe({ type: first-input, buffered: true });
}7.累计位移偏移 CLS经过计算未在用户输入 500 毫秒内出现的布局偏移的偏移分数总和来测绘内容的不稳定性⻚⾯渲染过程中忽然插⼊⼀张巨⼤的图⽚或说点击了某个按钮忽然动态插⼊了⼀块内容等等相当影响用户体验这个指标便是为这种状况⽽⽣的,计算⽅式为:位移影响的⾯积 * 位移距离CLS 举荐值为小于 0.1CLS 较差的最平常原由于:无尺寸的图像无尺寸的宣传、嵌入和 iframe动态注入的内容引起不可见文本闪烁 (FOIT)/无样式文本闪烁 (FOUT) 的网络字体在更新 DOM 之前等待网络响应的操作
function getCLS() {
try {
let cumulativeLayoutShiftScore = 0;
const observer = newPerformanceObserver((list) => {
for (const entry of list.getEntries()) {
// Only count layout shifts without recent user input.
if(!entry.hadRecentInput) {
cumulativeLayoutShiftScore += entry.value;
}
}
});
observer.observe({type: layout-shift, buffered: true });
document.addEventListener(visibilitychange, () => {
if (document.visibilityState === hidden) {
// Force any pending records to be dispatched.
observer.takeRecords();
observer.disconnect();
console.log(CLS:, cumulativeLayoutShiftScore);
}
});
} catch (e) {
// Do nothing if the browser doesnt support this API.
}
}
8.谷歌标准
LCP(Largest Contentful Paint - 最大内容绘制时间)=> 加载性能FID(First Input Delay - 首次输入延迟)=> 交互性CLS(Cumulative Layout Shift - 累计位移偏移)=> 视觉稳定性
9.怎样运用vue:定义公用办法类,common.js,mounted 阶段页面进行挂载,$nextTick()里对响应办法进行运用react:hooks useEffect()中运用react.useEffect(() => {}, []);机构内部运用打点系统:运用 echars 绘制,运用均值统计、百分位数统计、样本分布统计输出性能
优化办法
1.LCP
影响原因 缓慢的服务器响应速度阻塞渲染的 JavaScript 和 CSS缓慢的资源加载速度客户端渲染
提高办法 提⾼带宽(⽹速)需要使⽤ webpack 进⾏ tree-shaking使⽤路由懒加载,仅有在使⽤的时候在进⾏路由加载安排 CDN,缩短⽤户与节点之间的距离(⽹速)建⽴缓存,提⾼下次加载速度。开启 gzip 压缩。不要在头部添加任何 script 标签,或运用 js 异步加载 defer。针对少量⼩图标(单个尽可能不要超过 10K 的),咱们能够使⽤ url-loader 打包。或使⽤将图标转化为字体库,异步进⾏加载。针对⼤图标的话,需要做到在展示的时候再去加载。亦便是当图⽚显现到浏览器窗⼝的时候再去加载,⽽不是⾸屏的图⽚所有加载。
2.FID
影响原因 针对⽤户可操作时间,影响⼀个是注册的事件是不是能够被执⾏(说的通俗点便是 JS 脚本是不是加载完毕),以及是不是存在⻓任务。提高办法 分割长任务对⽂件进⾏懒加载,不要⼀次性把所有的 JS 加载出来。这就需要使⽤路由懒加载,在转到某个路由的时候,再去加载他的脚本资源。这般就能够保准 JS 加载速度的优化。不要在响应事件⾥有太多的运算,引起卡顿。倘若确有需要,应当开启webWorker,新起线程运算。3.CLS
影响原因 无尺寸的图像无尺寸的宣传、嵌入和 iframe动态注入的内容引起不可见文本闪烁 (FOIT)/无样式文本闪烁 (FOUT) 的网络字体在更新 DOM 之前等待网络响应的操作
提高办法 设置照片的时候给宽高预留后续动态插进的内容:骨架屏倘若经常需要变动的元素,脱离⽂档流,或是占据位置,只是隐匿文本字体等资源预加载倾向于选取 transform 动画,而不是触发布局偏移的属性动画
实战
整体优化思路及解析: 从浏览器输入 url 到页面各周期做了什么,进行性能优化按照前端性能指标进行优化框架特有的性能优化点:小程序分包、vue 路由按需加载等优化办法:研发规范、技术架构设计、系统架构设计
1.浏览器加载优化
DNS 预解析、预链接<!-- 开启隐式预解析:默认状况,浏览器对a标签中与当前域名不在同一域的关联域名进行预获取且缓存结果,针对https失效 -->
<meta http-equiv="x-dns-prefetch-control" content="on" />
<!-- 只解析域名,不进行资源下载 -->
<link rel="dns-prefetch" href="http://www.baidu.com" />
<!-- 将会做 DNS 解析,TLS 协商和 TCP 握手 -->
<link rel="preconnect" href="//baidu.com" />
<!-- 在浏览器空闲时下载资源 -->
<link rel="prefetch" href="https://css-tricks.com/a.png" />
<!-- 浏览器会提前完成所有的资源加载,执行,渲染并保留在内存里 -->
<link rel="prerender" href="https://css-tricks.com" />
<!-- 提前下载资源,影响资源加载次序,后置下载资源前置下载 -->
<link
rel="preload"
href="https://fonts.gstatic.com/s/sofia/v8/bjl.woff2"
as="font"
crossorigin="anonymous"
/>
<!-- ⽂件加载完成后,会执⾏此脚本,执⾏次序⽆法保准,先加载完成的先执⾏ -->
<script src="./static/demo1.js" async></script>
<!-- 延迟执⾏脚本,解析完</html>后执⾏,执⾏次序不变 -->
<script src="./static/defer-demo1.js" defer></script>
更加多内容见前端性能优化之 rel=“prefetch“预/懒加载功能
http 请求周期减少 http 请求恰当利用时序:资源合并(雪碧图)、运用 promise.all 并发请求减少资源体积:减少 cookie 信息、照片格式优化、gzip 静态资源压缩、webpack 打包压缩恰当利用缓存:cdn、http 缓存(强缓存和协商缓存)、本地缓存(localStorage、sessionStorage)
浏览器渲染周期:下载 css 并解析、下载 js 文件并解析会影响页面首屏渲染减少重排重绘,尽可能运用 css 动画,或添加 will-change 属性script 脚本放在 body 元素中⻚⾯内容的后⾯,避免 JS 阻碍 html 解析,减少⽩屏时间css 文件尽可能放在 head 中,尽快下载和解析运用预解析和异步加载:prefetch、prerender、preload、async、defer服务器端渲染 ssr资源按需引入:路由懒加载,组件库按需引入
2.技术框架路由懒加载组件按需引入:babel 插件转换webpack 打包优化配置:资源压缩、资源拆分安排至 cdn(externals)小程序:分包加载、setData 操作优化、限频接口调用优化等
3.架构优化cdn 预热nginx 缓存配置、gzip 压缩开启ssr 及预渲染后端 bigpipe 引入:动态网页加载技术
4.bigpipe 框架
bigPipe是由于 facebook 提出来的⼀种动态⽹⻚加载技术。 它将⽹⻚分解成叫作为 pagelets 的⼩块,而后分块传输到浏览器端,进⾏渲染。 它能够有效地提高⾸屏渲染时间。bigpipe 的适⽤是服务端进⾏渲染,而后将⼀块⼀块的⽂件传递给前端。
|