从一个经典的面试题引出文章内容:
输入 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);
// ...
使用开发者工具中的LighthouseTab
或者使用 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 的适⽤是服务端进⾏渲染,然后将⼀块⼀块的⽂件传递给前端。