从输入URL到页面渲染及页面渲染优化(必知必会)

619 阅读20分钟

1,从输入URL到最终页面渲染

  • 1,HSTS校验:HSTS是一种web安全协议,如果目标资源网站使用了HSTS,且客户端发送的是HTTP请求,那么该协议会强制客户端发送HTTPS请求以确保数据传输安全。

    • 1.1:查询HSTS预加载列表,如果当前资源请求地址在该列表中,浏览器将使用HTTPS发起该资源请求

    • 1.2:如果HSTS预加载列表中无该地址,浏览器将发送HTTP请求,目标服务器返回资源重定向报文,浏览器根据重定向报文中资源位置,再次发起请求,不过这次使用的是HTTPS请求。

  • 2,缓存查找:首先根据URL检查本地缓存资源中是否当前缓存资源,有缓存取缓存,无缓存进行DNS解析

    • 2.1:如果存在浏览器缓存资源,首先检查是否命中强缓存,命中直接使用缓存数据

    • 2.2:如果未命中强缓存,则进行协商缓存,携带协商缓存字段向服务器发起请求,服务器根据协商缓存字段判断浏览器缓存资源是否可用,可用则返回304状态码,告诉浏览器使用本地缓存,不可用服务器直接返回资源。

    • tips:关于协商缓存是否需要进行DNS解析:个人理解为如果存在该资源的缓存即该资源曾经访问过,所以正常情况下浏览器首次访问域名进行DNS解析,都会将获取到的IP地址时都会将其缓存,所以协商缓存时直接取DNS缓存的IP地址即可。所以还是需要的,因为DNS缓存查询也是DNS解析一部分。

  • 3,DNS解析:DNS解析为获取域名对应的IP地址的过程。如果是IP地址格式的URL,则不需要进行DNS解析,域名格式需要进行DNS解析。

    • 3.1:首先查询DNS缓存,查看缓存中有没有当前域名的DNS记录,有的话直接使用对应IP地址,无则向DNS服务器查询DNS记录。

      • 3.1.1:首先在浏览器的DNS缓存中查询

      • 3.1.2:如果浏览器的DNS缓存中无记录,则在操作系统中DNS缓存中查询

      • 3.1.3:如果操作系统DNS缓存无记录,则在路由器DNS缓存中查询

    • 3.2:如果DNS缓存中查找不到当前域名DNS记录,则向DNS服务器发起查询请求

      • 3.2.1:浏览器通过操作系统的DNS客户端向本地DNS服务器发起DNS查询。本地DNS服务器运行在当前主机上,且本地DNS服务器的功能只是向不同域名服务器发出迭代查询请求,从域名服务器上获取当前域名对应的IP地址

      • 3.2.2:本地DNS服务器带着域名向根域名服务器发起查询,根域名服务器根据地址的顶级域名找到顶级域名服务器地址返回给本地DNS服务器。根域名服务器也不保存DNS记录,他的职责就是返回域名对应的顶级域名服务器地址

      • 3.2.3:本地DNS接收到顶级域名服务器地址,带着域名向顶级DNS服务器发起查询,顶级域名服务器根据域名的二级域名服务器地址返回给本地域名服务器。顶级域名服务器也不保存DNS记录,他的职责就是返回域名对应的二级域名服务器地址

      • 3.2.4:如果当前域名还存在三级域名,四级域名,则继续按照3.2.2,3.2.3这种方式继续迭代查询,这里假设二级域名服务器地址持有当前域名DNS记录。

      • 3.2.5:本地DNS接收到二级域名服务器地址,带着域名向二级DNS服务器发起查询,二级DNS服务器发现自己持有当前域名的DNS记录,于是将该域名IP地址返回给本地DNS服务器。权威DNS服务器即持有当前域名记录的DNS服务器,这里的二级DNS服务器就是权威DNS服务器

      • 3.2.6:如果当前访问域名其服务器做了CDN处理,那么权威DNS服务器通过负载均衡系统找出距离用户最近响应最快的缓存服务器IP地址返回给本地DNS服务器

      • 3.2.7:本地DNS服务器获取到权威DNS服务器返回的IP地址,将IP地址返回给本地DNS客户端,DNS客户端将IP地址返回给浏览器,DNS解析结束。

  • 4,建立TCP连接:建立TCP连接即三次握手,之后进行可靠数据传输。 TCP协议属于传输层,面向字节流,会将应用层传输过来的数据(过大)分割成多个报文段,并为每个报文段添加20字节大小的TCP表头,表头中包括了源端口号,目标端口号,TCP报文段序列号,窗口字段,校验和字段,控制标识码等字段,用来控制数据可靠传输。

    • 4.1:第一次握手:客户端发送TCP报文段,表头中控制标识码SYN=1:表示客户端希望建立TCP连接,序列号seq=x:表示当前报文端序列号为x。 (第一次握手不可以携带数据)

    • 4.2:第二次握手:服务端接收到客户端发送的数据,返回确认TCP报文段,表头中控制标识码SYN=1:表示服务端同意建立TCP连接,控制标识码ACK=1:刚才客户端发送过来的数据被正确接收,回应序号ack=x+1:表示该报文段为seq=x的确认报文端且希望下一次客户端发送序列号为seq=x+1的包,序列号seq=y:表示该报文端序列号为y。(第二次握手不可以携带数据,且该次握手服务端知道自己的接受能力正常)

    • 4.3:第三次握手:客户端接收到服务端发送回来的数据,继续向服务端发送TCP报文段,表头中控制标识码ACK=1:刚才服务端发送过来的数据被正确接收,回应序号ack=y+1:表示该报文段为seq=y的确认报文端且希望下一次客户端发送序列号为seq=y+1的报文段(第三次握手可以携带数据,且该次握手客户端知道自己的发送与接受能力均正常)

    • 4.4:服务端接收到客户端第三次握手发来的数据,知道自己的接受能力也正常。至此客户端与服务端TCP连接建立完成

    • 为什么要进行三次握手:

      • 保证客户端与服务端收发能力都正常

      • 防止滞留的握手历史包引起数据连接异常: 假设两次握手即可建立连接,那么就可能出现这样情况

        • 首次建立TCP连接第一次握手包滞留,客户端重传第一次握手数据包,服务端返回第二次握手,完成连接,完成数据传输,断开连接。断开连接后,首次滞留的握手包才到达服务器,服务器以为客户端又建立连接,于是返回第二次握手建立连接,但此时客户端才不需要建立连接,于是服务端建立了一个无效连接,导致资源浪费。
      • 初始序列号(ISN)同步: 如果两次握手,那么第二次握手服务端交给客户端的ISN,服务端无法知道客户端收到没有。所以需要客户端给个确认,保证双方同步好自己的ISN,没有出错。如果初始ISN出错,那么后续数据传输包的序列号肯定错乱,那么TCP可靠连接则无从谈起。

    • 三次握手可以携带数据吗:第一次与第二次不可以,第三次可以。

      • 第一次与第二次不可以携带数据一方面此时还不能确定对方的收发能力均正常,如果异常,那么这个数据就无法处理。

      • 其次,假设第一次握手可以携带数据,如果攻击者发送大量第一次握手连接,且每个第一次握手数据中携带大量数据,那么服务端就要花费很多时间去处理这些数据,没空处理别的事,这样服务器就很容易遭受攻击。所以第一次握手不可以携带数据可以使服务器更不容易被攻击。

      • 第三次可以发送数据是因为客户端知道服务端已经建立连接状态(服务端收发能力正常),所以可以携带数据也是情理之中。

    • 为什么四次挥手中的客户端发送第四次挥手之后(TIME_WAIT)需要等待2MSL之后才会断开连接?

      • MSL即报文在网络中最大生存时间,2MSL即2倍报文最大存活时间,这个等待的意义在于 如果第四次挥手数据丢包,服务端无法收到第四次挥手,则服务端不能断开连接(closed),服务端会重发第三次挥手数据,期望客户端收到重新给予第四次挥手。所以客户端等待2MSL的目的就是如果第四次挥手丢包,服务器重发第三次挥手,客户端能够及时重新发送第四次挥手数据,否则客户端不等这段时间,离开了,但第四次挥手丢包了,服务端就会不停重发第三次挥手,从而浪费服务器资源。而2MSL正好是这个一去一回(第四次挥手=>第三次挥手)两个数据包存活时间,所以这个时间是合理的。不多也不少。这个2MSL倒计时由时间等待计时器完成。计时结束,服务端没有发送第四次挥手,则表明服务器连接断开成功。此时客户端也可以断开连接了。
    • 什么是半连接队列:服务端接收到客户端发起的第一次发起握手数据(SYN=1),客户端进入SYN_SEND状态,服务端处于SYN_RCVD状态,此时双方还没完全建立连接,客户端第一次握手数据会放在一个队列中,同时向客户端发送第二次握手数据。此队列即半连接队列。

    • 半连接队列满了会怎样:

      • 如果服务端未开启syncookies=0,那么半连接队列满了,就会丢弃第一次握手发送过来的数据,且不进行第二次握手,客户端就会一直重发第一次握手数据最后导致错误

      • 如果服务端设置了syncookies=1,那么半连接队列没有理论最大值,因为此时用不到半连接队列。握手信息都通过syncookie实现(SynCookie见后面《防御SYN flood攻击的手段》)。

    • 什么是全连接队列:服务端除了半连接队列也会维护一个全连接队列,当客户端发起第三次握手,服务端接收到第三次握手数据。

      • 此时如果全连接队列未满,就会将半连接队列中的数据放入全连接队列中,供应用层使用。
      • 此时如果全连接队列满且tcp_abort_on_overflow = 0,服务端会丢弃客户端第三次握手发来的数据,且隔一段时间向客户端重发第二次握手数据。
      • 此时如果全连接队列满且tcp_abort_on_overflow = 1,服务端会发送一个reset包给客户端,表示废掉当前握手过程与当前连接。
      • tcp_abort_on_overflow为系统配置,当系统守护进程太忙无法处理新的连接,期望断开当前连接可以将该字段设置为1,该字段完全根据系统配置,配置0就是0,配置1就是1
    • ISN是什么以及ISN为什么是随机生成的:ISN(Initial Sequence Number)初始序列号,即双端建立连接时双方首次确定的TCP包序列号,ISN并不是固定的,是随机生成的一个序列号。随机生成的原因主要有以下两个原因:

      • 1,如果序列号都是固定的,那么显然攻击者很容易猜出后续包序列号,从而伪造报文破坏数据通讯。虽然攻击者通过抓包也能判断出后续序列号,但是抓包只限于同一网络中,而对于非同一网络攻击者是无法抓包, 所以非固定的序列号主要是防止非同一网络中的攻击者的攻击(提高了攻击者攻击难度)。

      • 2,防止上次TCP连接中由于滞留等原因的数据包,恰好在本次TCP连接中落入对方的接收窗口内(恰好上次TCP连接滞留包序号与本次TCP连接即将接收包的序列号相同)导致通信数据错误。

    • SYN FLOOD攻击:即大量发送第一次握手数据,导致服务端半连接队列填满,使得服务器无法处理正常的握手连接。 因为第一次握手后,服务器向客户端发送第二次连握手数据,此时服务器处于SYN_RCVD状态,连接既没有完全建立,也没有完全断开,等待客户端发送第三次握手数据,如果客户端迟迟不发送第三次握手数据,那么LINUX服务端默认重发5次第二次握手数据给客户端,一共耗时63s,五次之后还没收到客户端第三次握手数据,服务器才会断开当前连接,因此攻击者可以利用这点发送大量第一次握手数据对服务器进行攻击。

    • 防御SYN flood攻击的手段:

      • Linux实现SYNCookie机制,即服务端收到第一次握手信息,即将第一次客户端握手信息编码在ISN(服务端初始包序列号)中,在第二次握手时返回给客户端,假设编码后的ISN(第二次握手的ISN)为3000

      • 客户端收到第二次握手,回应第三次握手,第三次握手中确认ACK中的ack(确认序号)应该是3000+1

      • 服务端收到第三次握手,拿到客户端短确认序号3001,3001-1即得到3000,对比服务端第二次握手计算出的服务端ISN,发现是3000,相同,即表明是本次握手,之后连接即可建立。

      • 这样就不会使用到半连接队列,从而防止SYN FLOOD攻击导致半连接队列满。

  • 5,SSL握手:如果是用HTTPS发起请求则需要进行SSL/TLS握手获取对称加密密钥,从而对数据进行加密传输。 HTTPS即在HTTP请求基础上使用SSL/TLS协议对HTTP传输的数据进行加密,从而保证数据安全性,TLS协议即标准化后的SSL协议(SSL3.1===TLS1.0),SSL握手协议即使用非对称加密获取对称加密密钥过程。

    • 5.0:使用三次握手建立TCP连接后进行SSL四次握手
    • 5.1:握手1:发送方给出TLS协议号,客户端支持的加密方法以及客户端生成的随机数A
    • 5.2:握手2:接收方接收到发送方数据,保存随机数A,并生成随机数B,向CA机构获取证书(包含接收端的非对称加密公钥),以及客户端与服务端使用的加密方式发送给发送方
    • 5.3:握手3:发送端接收到服务端数据,验证证书,获取证书内接收端公钥,使用公钥加密随机数C(即预主密钥)发送给接收端
    • 5.4:握手4,接收端接收到随机数C,并结合先前的随机数A,以及随机数B按照之前与发送端确定的加密方法生成对称加密密钥,并通知发送端之后通讯使用先前确定的加密方式以及密钥加密通讯,结束握手阶段。(发送端同样拥有随机数A,B,C,发送端按照先前确定的加密方法最终生成与服务端相同的对称密钥,之后二者通讯使用该密钥进行加密解密数据)
  • 6,确认资源请求方MAC地址:使用ARP协议获取目标MAC地址,IP协议属于网络层,当接收到传输层TCP报文,会将其添加消息头,包括协议,源IP,目标IP地址等,同时因为MAC地址才是计算机唯一标识,IP地址时可变的,我们需要通过目标IP地址得到对方MAC地址才知道真正接收方是谁,最后将数据发送过去。这一部分属于网络层。

    • 6.1:通过子网掩码对源地址与目标IP地址进行计算,判断双方是否在同一子网内。IP地址由网络部分与主机部分构成,子网掩码与IP地址配套出现,子网掩码网络部分全是1,主机部分全是0,将子网掩码与双方分别进行AND运算结果一样则在同一子网内,反之不在
    • 6.2:使用ARP协议获取对方MAC地址,即如果发送方与接收方在同一子网内,则向子网内发起广播,子网内所有计算机接收到广播,将自身IP地址与数据包中IP地址进行比对,不同丢弃,相同返回自身MAC地址。数据包MAC地址部分填上特殊MAC地址用来告知接收方是当前通讯行为是数据发送还是获取MAC地址,且如果对方与发送方不在同一子网内,则将数据包发送给网关转发,网关接收到数据再返回给发送方
  • 7,将数据分帧,使用物理手段发送出去:根据以太网协议将数据分成帧形式,然后将其使用物理形式比如电信号的方式发送出去,以太网协议属于数据链路层,单纯的0,1电信号没有意义,而以太网协议将电信号进行分组,规定每一组电信号什么意思,这样发送方与接收方根据以太网协议就能生成与解析出电信号的含义,每组电信号就是帧,帧头部包括双方MAC地址等信息。属于数据链路层与物理层。

  • 8,目标服务器接收到数据,进行处理:

    • 8.1:发送方发送的http报文从应用层,传输层,网络层,数据链路层,物理层数据传输到达目标服务器
    • 8.2:目标服务器从物理层到应用层解析获取到发送方的数据进行处理,再从应用层到物理层返回发送方请求的数据
    • 8.3:发送方接收到目标服务器返回的数据,从物理层到应用层解析出目标服务器返回的http报文
  • 9,发送方获取到目标服务器返回的数据,浏览器根据这些数据进行页面渲染:

    • 9.0:如果想更详细的了解这一块,建议跳到我的另一篇文章《从图层的角度去看关键渲染路径的布局到合成》

    • 9.1,DOM树解析阶段:将html解析成dom树,主要过程分为解码,令牌化,生成dom节点,根据dom节点关系构建dom树。dom树的构建是在网页内容下载过程中创建的,即获取到html字节,解析字节到构建dom树的过程中同时还在继续下载其他内容比如图片,样式,脚本等

    • 9.1.1:解码:将字节数据根据其编码方式转换成对应字符

    • 9.1.2:令牌化(token):根据W3C规范将字符转换成令牌(token),令牌匹配都有自己规则,比如<p class="a">text text text</p>,当我们匹配到<我们并不知道它是哪一个token,继续匹配下一个字符<p,这时我们知道这时这个是p标签的开始的token,按照这种方式根据不同的token规则将字符串全部转化成token,如图

    • 9.1.3:根据令牌生成对应dom节点

    • 9.1.4:根据dom节点关系生成dom树

  • 9.2,CSSOM树解析阶段:将CSS解析成CSSOM树(CSSOM:CSS object model),与html解析过程类似,解码,令牌化,生成CSS节点,生成CSSOM树。css解析与dom解析是并行的

  • 9.3:布局阶段:布局阶段既计算出DOM树中可见元素的几何位置

    • 9.3.1,创建布局树:从根节点开始遍历dom树中的可见节点(display:node与head标签内元素为不可见节点,在此阶段会过滤掉),将可见节点添加到布局树中

    • 9.3.2,布局计算:将节点与对应的样式对象合并,计算出展示节点具体样式

  • 9.4:分层阶段:布局树中拥有层叠上下文的属性会单独提升成渲染图层(比如dom根元素,opacity属性,z-index,绝对定位等都会形成渲染图层),同时,对于内容溢出及需要裁剪元素导致出现滚动条也需要单独提升为渲染图层

  • 9.5:绘制阶段:布局阶段既计算出DOM树中可见元素的几何位置

    • 9.5.1,创建绘制列表:将绘图指令按照顺序创建绘制列表,既绘制指令列表
    • 9.5.2,栅格化(或者叫它光栅化):栅格化既将当前图层划分为图块(所以绘制是以图层为单位),然后将图块生成对应的位图(位于视口附近的图块优先去生成位图)
  • 9.6:合成&&显示阶段:

    • 9.6.1,GPU将上传过来的位图进行合成,合成完毕之后,将发出一个绘制图块的命令DrawQuad
    • 9.6.2,浏览器接受到该命令将从GPU内存中读取合成后的图像,最终展示出来
  • diplay:nonevisiblity:hidden区别:render树只包括需要渲染节点,所以diplay:none的dom元素或者head节点就不会出现在render树中,而visiblity:hidden会在render树中存在,它只是看不见,但是位置还是存在的。

    • load事件与DOMContentLoaded先后:
    • DOMContentLoaded指当dom加载完成,不包括样式表与图片,即触发
    • load事件指渲染完毕,即页面上所有dom,样式,脚本图片等都加载完毕触发
    • 所以DOMContentLoaded先于load事件
  • js加载解析为什么会阻塞dom解析:js引擎与渲染引擎是互斥的,js是可以操作dom的,为了防止dom解析与js执行冲突(比如dom加载一个节点,js删除该节点),所以js会阻塞dom解析,当js加载解析完毕,dom恢复解析,这也是为什么需要将js文件放到body底部原因。

  • css解析会阻塞JS吗?:会,因为js不仅可以操作dom,也可以操作css,而不完整的CSSOM是不可以使用的,所以js会等到CSS加载解析完毕,再执行。

  • CSS会阻塞渲染呈现吗?:会,因为布局阶段布局树的生成需要dom解析结果与css解析结果,所以面渲染会等待CSS解析完毕。为了保证尽早尽快将CSS加载解析,所以css放在head标签内。

  • js的defer与async属性:

    • defer:通常情况下js加载执行会阻塞dom解析,但如果script标签加入defer属性,那么js下载的时候将不会阻塞dom执行,但会延迟DOMContentLoaded执行,如果js下载完但是dom还没解析完,那么会等dom解析完才执行js,之后才触发DOMContentLoaded事件。如果有多个js下载文件,那么执行时也是按顺序执行的。
    • async:使用async属性时,js加载也不会阻塞dom解析,但是区别于defer,async是加载完js立即执行,DOMContentLoaded前后都有可能,所以多个js文件可能执行过程是无序的,谁先下载完谁执行,async属性会阻塞load事件
    • 二者对内联脚本不起作用,且js创建js标签默认为async,defer与async同时出现async优先级更高除非浏览器不支持async。
  • 为什么js是单线程:js作为浏览器脚本语言,设计即服务于浏览器与用户互动及操作dom,单线程意味着同一时间只做一件事,如果是多线程将带来很复杂的同步问题,比如一个线程删除了dom节点另一个线程添加了dom节点,那么以谁为准?

  • 什么是回流(reflow):当我们对dom修改引发dom几何属性变化,浏览器需要重新计算当前dom属性,同时也会导致其他元素几何属性变化,最后将计算结果绘制出来(相当于布局以及之后的流程都得执行一遍),这个过程就是回流也叫做重排。

  • 什么是重绘(repaint):当我们对dom修改引发其样式变化时,但不影响其几何属性(比如颜色变化),浏览器不需要对dom进行回流,只需要重新绘制(绘制及绘制之后的流程都得执行一遍),这个过程叫做重绘。

  • 为什么操作dom慢:js执行属于js引擎的事,dom呈现属于渲染引擎的事,虽然js很快,但js操作dom肯定需要两个引擎线程交互,这个开销是无法避免的,而且dom呈现可能会触发回流重绘这个过程也是很大开销,所以操作dom很慢,我们需要将多次对dom操作合并成一次减少开销。

  • 什么是关键渲染路径:从接收到资源请求的字节信息到最终呈现这一过程叫做关键渲染路径,优化网页性能就是优化关键渲染路径。

  • 10:断开TCP连接:使用四次挥手断开TCP连接

    • 10.1:第一次挥手:客户端发送TCP报文段,表头控制标识码FIN=1:表示客户端希望断开连接,序列号seq=x:表示该包序列号为x
    • 10.2:第二次挥手:服务端接收到客户端数据,返回TCP报文段,表头控制标识码ACK=1:表示刚才的数据正确接收,回应序号ack=x+1:表示当前报文段为seq=x的确认报文.
    • 10.3:过了一会,服务器这边该发送给客户端的数据都发完了,服务器觉得此时可以断开连接了,于是开始第三次握手
    • 10.4:第三次挥手:服务器主动向客户端发送TCP报文段,控制标识码FIN=1:表示服务器也希望断开连接,序列号seq=y:表示当前包序列号为y
    • 10.5:第四次挥手:客户端接收到服务端数据,知道服务器这边也可以断开连接了,于是向服务器发送TCP报文段告诉服务器大家可以散了,表头控制标识码ACK=1:表示服务端发送的数据正确接收到了,回应序号ack=y+1表明当前报文为服务端序列号seq=y的回应报文。
    • 10.6:四次挥手结束,客户端与服务端断开TCP连接。
    • 为什么需要四次挥手?:因为如果客户端发送断开连接请求,此时服务端可能还有数据要发送给客户端,服务端不一定可以立即断开连接,所以服务端先回应一个确认ACK告诉客户端你先等会,等我这边数据处理完,我在通知你断开连接,于是当服务端数据处理完便进行第三次挥手告诉客户端服务端这边也可以断开连接了,考虑到第三次挥手可能丢包或者时延,导致服务端断开连接了,客户端还在傻等着,所以还需要客户端进行第四次握手告诉服务端我知道了。保证双方连接完全断开。
    • 为什么第二次挥手与第三次挥手之间还需要等一段时间?:客户端期望断开连接的时候服务端可能还有数据需要继续发送,所以这段时间服务端将这些数据发送给客户端,防止客户端自己断开连接跑路,服务端的数据发送给客户端的其他应用程序上去导致错误。
    • 为什么要等2MSL才客户端才进入关闭状态?:客户端收到服务器端第三次握手回应第四次握手的时候,会开启2MSL定时器,等待2MSL时间(1MSL即报文段最大存活时间),如果没有再次收到服务端第三次握手包即可以断开连接,如果再次收到了服务端第三次握手,说明客户端第四次握手数据丢失或者时延服务端迟迟接收不到该确认,于是服务端重发第三次握手,客户端再次接收到第三次握手重置2MSL定时器,并重发第四次握手数据,所以等待2MSL就是防止第四次握手数据的丢失,导致客户端断开连接,服务端没断开,一直重发第三次握手。同时2MSL的含义即保证客户端等待时间足够报文段的一来一回。

2,页面渲染优化

  • 页面渲染优化:从请求页面资源=>获取页面资源=>页面渲染(关键路径渲染)过程优化
    • 请求页面资源=>获取页面资源过程优化:

      • HSTS:使用HSTS
      • 缓存策略:合理使用缓存策略,比如:对于固定不变资源提高缓存时间
      • CDN加速:使用CDN缓存,提高之后数据响应速度
      • HTTP2:使用更高版本的HTTP协议,比如HTTP2优化了1.1中线头阻塞,且新增了头部压缩,服务端推送(加载资源时发现由于存在服务端推送资源已经存在于缓存中,所以可以提高资源加载速度)等
      • TLS1.3:使用更高版本的TLS协议,比如TLS1.3中将TLS握手阶段从四次降低到两次
    • 获取页面资源=>页面渲染过程优化:

      • DOM优化:
        • 规范开发代码:合理控制dom层次,减少dom层级,提高DOM解析速度
        • 减少DOM数量:使用虚拟列表展示大列表
        • 提升复合图层:复杂DOM元素或者固定不变的DOM区域提升为复合图层,避免其他图层的回流重绘
      • CSS优化:
        • CSS尽量提到DOM头部:尽早解析CSS创建布局树
        • 避免使用@import加载CSS:@import会在所有资源加载完毕才会去加载,会增加关键渲染路径路径往返时间
        • 打包CSS:使用打包工具将CSS资源打包成一个资源,减少资源请求数量
      • JS优化:
        • 打包JS:使用打包工具压缩精简代码,并将JS打包成一个文件,减少JS资源体积与JS资源请求数量
        • 异步加载JS:使用defer/async异步加载JS资源,避免阻塞DOM解析
        • 避免JS频繁操作DOM:减少回流重绘
      • 图片优化:
        • 雪碧图
        • 图片懒加载:懒加载配合prefetch使用更好
      • 其他优化:
        • 使用preload:提前加载资源,浏览器解析到该资源时立即加载该资源,但不执行,当资源被使用的时候立即执行
        • 使用prefetch:对于能够预测的用户行为,比如懒加载,点击进入其他页面可以使用prefetch在浏览器空闲时间提前加载好资源,使用的时候(加载图片,页面跳转)就可以直接拿到资源而不需要再进行网络请求
          • preload与prefetch使用方法相同,如果期望更快速的加载资源,可以直接在http响应头中加上preload或者prefetch字段,这样在页面解析之前就可以开始加载资源

                <!-- link 模式 -->
                <link rel="prefetch" href="/path/to/style.css" as="style">
            
                <!-- HTTP 响应头模式 -->
                Link: <https://example.com/other/styles.css>; rel=prefetch; as=style
            
        • 使用dns - prefetch||preconnect:针对于当前资源大概率被访问可以设置该属性,提前完成DNS解析或者提前于服务器完成连接
        • 使用服务端渲染:
        • 使用webacpk打包资源:精简资源,减少资源体积,提高资源传输速率
        • 使用更高版本的react,webapck等:高版本优化好

感谢参考: