从一个经典的面试题引出文章内容:
输入 url 到页面最终呈现都发生了什么?
双方确认过信息,可以建立起通信
,见图 1 山谷的两边
,想要相互聊天
,但是又不确定自己喊一嗓子之后对方到底能不能听到,然后就双方开始聊天之前先互相喊几嗓子,确认一下双方是不是都可以听到B不知道A能不能听到自己说话
,然后 B 就把 A 第一次的 x 和自己想说的话 y 一起喊给了 AA就确认了B可以听到自己的话
,然后 A 再把 B 说的话 y+1 喊给 B,这样B收到A的信息之后就知道A可以听到自己的话
,后面双方就可以愉快的聊天了重复
或交叉
的情况,比如当遇到 JavaScript 或者动态资源时,可能需要重新构建或更新某些部分。三次握手与四次挥手
浏览器解析响应内容并渲染页面
建立在IP协议
之上的。TCP 协议负责在两台计算机之间建立可靠
连接,保证数据包按顺序
到达。三次握手
建立连接,然后,对每个 IP 包编号,确保对方按顺序收到,如果包丢掉了,就自动重发。HTTP
协议、发送邮件的SMTP
协议等。面向无连接
的协议,TCP 必须建立可靠的连接不需要建立连接
,只需要知道对方的 IP 地址和端口号,就可以直接发数据包。但是,能不能到达就不知道了。所以 UDP 传输的数据不可靠
面向报文
的,没有拥塞控制,所以速度快,比较多媒体通讯,稳定
的直播环境,都是采用的 TCP 链接一个
请求,当服务器响应后就会关闭这次连接,下一个请求需要再次建立
TCP 连接持续链接
(TCP 链接默认不关闭,可以被多个请求复用,不用声明 Connection:keep-alive)管道机制
,在同一个 TCP 连接里,浏览器允许多个请求同时发送,增加了并发性,进一步改善了 HTTP 协议的效率次序
进行的。如果前面有一个请求回应慢,就会有许多请求排队,造成“队头堵塞”
双工模式
,即:不仅客户端能够同时发送多个请求,服务端也能同时处理多个请求,解决了队头堵塞的问题多路复用
的技术,做到同一个连接并发处理多个请求
,而且并发请求数量比 http1.1 大了好几个数量级一问一答
的形式,上一个请求响应后才能处理下一个请求,由于浏览器有最大TCP连接的限制
(6 个),所以有了最大并发请求数的限制。单个TCP
连接上完成,消除了因多个 TCP 连接而带来的延时和内存消耗。单个连接上可以并行
的请求和响应,之前互不干扰二进制"帧"
的概念,http1.1 是基于“文本分割”
解析的协议换行符(br)
,或者说是空白行。处理顺序是串行
的,一个请求和一个响应需要通过一问一答
的形式才能对应起来“帧”和“流”
数据单位
,每个帧会标识出该帧属于哪个流,流也就是帧组成的数据流TCP连接中可以存在多条流
。帧中的标识
知道属于哪个请求队头堵塞
问题,极大的提高传输性能内容分发网络
的缩写,它是一种将互联网内容快速传输给用户的技术。CDN 通过在不同地区部署多个服务器,将内容缓存到离用户最近的服务器上,从而减少延迟和带宽
消耗。重新设置缓存
的过程。回源可以保证 CDN 节点总是能够提供最新和最完整的内容给用户,但也会增加源站的负载和流量成本。垃圾回收是一站自动管理内存的机制,js 中的 gc 主要有两种算法:引用计数、标记清除
var a = {};
var b = {};
a.b = b;
b.a = a;
可达
的对象,然后清除那些不可达的对象标记清除处理循环引用的例子:
var a = {};
var b = {};
a.b = b;
b.a = a;
a = null;
b = null;
这里,对象 a 和对象 b 互相引用,但在最后两行,它们都被赋值为 null,不再被任何变量引用。
如果使用引用计数算法,它们的引用计数仍然为 1,不会被回收。
但如果使用标记清除算法,它会从全局对象 window)作为根开始遍历所有的对象,发现 a 和 b 都不可达了(因为没有任何变量指向它们),就会把它们标记为垃圾,并在下一次 gc 时清除它们。
原理:就是在合适的时机,打上合适的时间戳,或者暴露出事件。然后通过这些时间戳之间的差值,得出⼀个耗时时间。这个耗时时间就可以反映出我们⻚⾯的相关性能。工具如下:
Performance 接口可以获取到当前页面中与性能相关的信息。它是 High Resolution Time API 的一部分,同时也融合了 Performance Timeline API、Navigation Timing API、 User Timing API和 Resource 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 |
PerformanceObserver.observe() :指定监测的 entryTypes
的集合。
当 performance entry 被记录并且是指定的 entryTypes
之⼀的时候,性能观察者对象的回调函数会被调⽤。
var observer = new PerformanceObserver(callback);// 直接往 PerformanceObserver() 入参匿名回调函数
// 成功 new 了一个 PerformanceObserver 类的,名为 observer 的对象
var observer = 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'] });
⽬前只能统计’CLS’ | ‘FCP’ | ‘FID’ | ‘LCP’ | ‘TTFB’ 。如果需要扩充的话,就可以使⽤上⾯的 Performance 进⾏更改
import { getCLS, getFID, getLCP } from 'web-vitals';getCLS(console.log);
getFID(console.log);
getLCP(console.log);
// ...
使用开发者工具中的Lighthouse
Tab
或者使用 Node CLI 的方式
npm install -g lighthouse
lighthouse
输入 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'] });
}
指的是⻚⾯上绘制了第⼀个元素的时间
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'] });
}
当 onload 事件触发的时候,也就是整个⾸⻚加载完成的时候
function getFirstPage() {console.log('FIRSTPAGE',performance.timing.loadEventEnd - performance.timing.fetchStart,);
}
⽤于记录视窗内最⼤的元素绘制的时间,该时间会随着⻚⾯渲染变化⽽变化,因为⻚⾯中的最⼤元素在渲染过程中可能会发⽣改变,另外该指标会在⽤户第⼀次交互后停⽌记录。
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'] });
}
FCP 指标后,首个长任务执行时间点,其后无长任务或 2 个 get 请求。
function getTTI() {let time = performance.timing.domInteractive - performance.timing.fetchStart;console.log('TTI', time);
}
function getFID() {new PerformanceObserver((entryList, observer) => {let firstInput = entryList.getEntries()[0];if (firstInput) {const FID = firstInput.processingStart - firstInput.startTime;console.log('FID', FID);}}).observe({ type: 'first-input', buffered: true });
}
位移影响的⾯积 * 位移距离
function getCLS() {try {let cumulativeLayoutShiftScore = 0;const observer = new PerformanceObserver((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 doesn't support this API.}
}
react.useEffect(() => {}, []);
影响因素
提升方法
影响因素
⻓任务
。提升方法
分割长任务
懒加载
,不要⼀次性把所有的 JS 加载出来。这就需要使⽤路由懒加载,在跳转到某个路由的时候,再去加载他的脚本资源。这样就可以保证 JS 加载速度的优化。webWorker
,新起线程运算。影响因素
提升方法
整体优化思路及解析:
更多内容见前端性能优化之 rel=“prefetch“预/懒加载功能
bigPipe是由 facebook 提出来的⼀种动态⽹⻚加载技术。
它将⽹⻚分解成称为 pagelets 的⼩块,然后分块传输到浏览器端,进⾏渲染。
它可以有效地提升⾸屏渲染时间。bigpipe 的适⽤是服务端进⾏渲染,然后将⼀块⼀块的⽂件传递给前端。