本文主要讲如何让用户快速看到首屏页面。主要影响因素是延迟和耗时的分析和渲染。安全部分其实也是优化的一部分。让我们关注网络部分。大致流程:DNS域名解析、TCP连接建立、下载资源、解析页面。文中描述的优化会尽量限于当时的分析过程。参考《计算机网络自顶向下方法》《Web性能权威指南》老生常谈——从输入url到页面显示在浏览器中输入一个URL会发生什么从浏览器多进程到JS单线程,JS运行机制是对DNS解析最全面的梳理什么是过程,求详细?前端性能优化***实践前端性能优化-justjavac浏览器渲染:流程与原理浏览器渲染流程与性能优化1.DNS域名解析一般来说,我们输入的url是一个域名,为了标识一个实体,TCP/IP通过IP地址来唯一确定一台主机连接到Internet,而DNS会帮助我们完成域名到IP地址的映射。以www.aaa.com为例,解析过程大致如下:processbrowserbrowserquerybrowsercache,no.本地浏览器客户端向系统询问服务器IP地址,调用本机DNS解析程序,检查本地hosts文件是否有域名映射关系,没有。查看本地DNS解析器缓存,没有。路由器缓存也可能存在于路由器缓存级别的本地DNS服务器中。本地DNS解析程序向本地DNS服务器发起请求,一般为TCP/IP参数中设置的***DNS服务器。它知道IP地址,一般会UDP协议。本地DNS服务器查找在本地区域文件中,没有。本地DNS服务器查询DNS缓存中是否存在,不存在。本地DNS服务器会根据转发器是否转发来判断是将请求发送给上级DNS服务器(相同的解析规则相同)还是直接发送给根DNS服务器(知道根DNS服务器的IP地址)已设置。根DNS服务器收到与DNS服务器的请求后,并不直接解析地址,而是知道每个VPN域中的一台服务器(如com域名服务器)的地址。如果是迭代查询,则返回本***域的DNS服务器的ip给本地DNS服务器。本地DNS服务器提取到***域的DNS服务器信息后,会向其发送请求。***域DNS服务器收到请求后会先查询自己的缓存,如果没有,则将负责的二级域名服务器(如aaa.com域名服务器)返回给本地DNS服务器,依次类推,直到找到目标域名映射信息或查询失败。找到映射信息后返回本机,中间层会缓存起来。查询方式递归方式:查询中途不会返回,得到最终结果后才返回信息。迭代法:就是在上面的本地DNS服务器和其他域名服务器之间直接查询的方法,找到一个可能的已知服务器地址,返回这个地址,重新发送解析请求。一般默认的方式是从本机递归到本地DNS服务器,在DNS服务器之间迭代查询。优化DNS的优化当然是为了减少DNS解析的时间。由于浏览器缓存机制的存在,我们只需要优化***访问即可(虽然我们现在只请求一个html文件,但是后面请求的html文件css/js/img中还是会有我们的),即适当减少解析的域名数量,并考虑其他优化机制,页面和页面内的资源可以发布到2-4个域名。2.建立连接。TCP连接完成后,浏览器终于??拿到了服务器IP。如果客户端要与服务器通信并传输消息,则需要打开TCP(一种传输层协议)连接。过程:客户端创建socket,向服务器目标端口发送连接建立请求,数据段包含位码SYN(连接建立标志位)=1,随机数seq(序号)=x,以及其他TCP标志和选项。服务器有一个欢迎套接字,专用于处理连接请求。收到连接建立请求后,设置码SYN和ACK(确认标志)为1,ack(确认号)=x+1,随机数seq=y,返回。客户端检查ack是否等于x+1,如果相等,则设置ACK为1,SYN为0,ack为y+1发送给服务端。欢迎套接字检查ack等于y+1且ACK等于1后,创建一个新套接字。这个套接字是通过源IP/源端口、目的IP/目的端口来标识的,然后客户端发送的数据就指向这个新的套接字。至此,TCP连接建立。简单地说://client:send({SYN:1,seq:x,...others})|↓//server:send({SYN:1,ACK:1,ack:x+1,seq:y,...其他})|↓//client:ack===x+1?send({ACK:1,SYN:0,ack:y+1,...其他}):'呵呵'|↓//server:ack===y+1&&ACK===1?newSocket():''SSL/TLS如果开启HTTPS加密,使用TLS前需要协商加密通道。过程:客户端TCP连接建立后,一些规范、随机数Random1、TLS协议版本、支持的密码套件列表,以及其他支持或需要的TLS选项以明文形式发送。服务端获取后续通信的TLS协议版本,从客户端提供的密码套件列表中选择一个,生成随机数Random2发送给客户端;附加自己的证书,并将响应发送给客户端;同时,它还可以发送一个请求,要求客户端提供证书和其他TLS扩展参数。客户端同上,可能会将其自己的证书发送到服务器。客户端收到服务器的证书后,通过证书链关系向根CA(证书颁发机构)验证证书的合法性。验证通过后,取出证书中服务器的公钥,生成一个随机数Random3,用服务器的公钥加密。Random3(premasterkey),发送给服务器;告诉服务器开始加密透明消息;客户端使用三个随机数和约定的加密方法生成会话密钥。生成之前握手信息的摘要,用sessionkey加密后发送,告诉服务器我握手完成了。除使用服务器公钥加密的新对称密钥外,所有数据均以明文形式发送。服务器使用私钥解密客户端发送的随机数,通过验证报文的MAC来检查报文的完整性,并以同样的方式生成会话密钥。解密客户端发送的完成消息,验证会话密钥是否正确。告诉客户端加密即将开始;并向客户端返回加密的完成消息。客户端用之前生成的会话密钥对该消息进行解密,判断会话密钥是否正确,如果正确则建立通道并发送应用数据。其中:会话密钥也可以称为协商密钥。会话密钥是对称密钥,对称加密和解密的速度非常快。服务器公钥和私钥是非对称密钥,非对称加解密速度很慢。使用非对称加密生成可靠的对称密钥,并使用对称密钥加密后续数据。上述带有序号的消息可以一次性发送,也可以依次分批发送。SSL和TLS可以视为一回事。服务器也可以使用自己的证书代替CA颁发的证书。优化我们需要优化TCP和SSL/TLS之间耗时的握手。有以下几个因素:数据往返延迟:主要受地理位置的影响,使用较近的服务器进行数据传输会减少数据往返的时间,我们可以将服务器部署在不同的地区(如:CDN,这样会也使用DNS解析,客户端访问域名到最近服务器的映射可能在DNS解析阶段完成),将数据放在靠近客户端的位置可以减少网络往返时间。证书链:其实数据往返时延的优化不仅仅针对TCP握手阶段,还针对后续基于TCP的数据传输,比如SSL/TLS握手以及后续的请求响应。那么证书链是影响SSL/TLS握手的重要因素。证书链是服务器发送给客户端的证书中的信息。由站点证书、中间证书颁发机构的证书、~~根证书~~(类似DNS域名解析服务器关系)组成。原因:TCP慢启动:由于TCP慢启动(为避免拥塞,TCP连接一开始只能发送较少的数据包,然后等待客户端确认,然后加倍,经过几次往返直到达到阈值)和TLS/SSL握手数据发送一般在TCP连接的慢启动阶段。过多的证书数据会超过TCP连接的初始值,从而使数据往返次数加倍。证书链验证过长:客户端浏览器在验证证书真实性时,会递归验证链中的每个节点到根证书,也会增加握手时间。方法:减少中间证书颁发机构的数量,优化为只有站点证书和一个中间证书颁发机构。不要添加根证书信息,浏览器内置信任列表中有根证书。握手次数:前两个优化都是为了优化握手时间,握手次数也是影响时延的重要因素。稍后当我们讨论批量请求时,我们会回到这个问题。初始拥塞窗口:适当增加初始拥塞窗口的大小,即增加TCP连接可以发送的初始数据包大小。3、获取页面响应重定向response如果服务器返回跳转重定向(非缓存重定向),那么浏览器将重新进行DNS解析,并与新的URL地址建立连接。所以应该避免不必要的重定向。页面资源响应浏览器获得html响应后,开始解析页面,进入准备渲染阶段。下载优化也会在后面说到大量请求的时候再说。4.解析和渲染页面我们需要把这个过程分成两部分,页面资源加载和渲染。页面资源加载浏览器在解析页面的过程中会请求页面中的js、css、img等外部资源。要建立连接,加载这些资源也需要建立TCP连接,直接使用也需要DNS解析和握手。这里优化请求的次数和频率比第一次请求页面资源时要高很多,所以这里着重介绍批量请求的优化。目前浏览器使用的HTTP协议版本多为1.1和2,有些不同,但底层使用TCP进行数据传输。由于TCP握手很耗时,而SSL/TLS更耗时,我们需要减少整个加载过程中需要建立的连接的数量和耗时。多路复用:HTTP1.1最好的方法是开启长连接:HTTP1.1默认提供了开启长连接的功能。与短连接(每次请求一个资源建立然后断开一个TCP连接)相比,同一个客户端socket(浏览器可能会开启多个端口进行并行请求)后续对同一个socket(域名+端口)的请求会多路复用一个TCP连接进行传输,直到TCP连接关闭。加速:SSL/TLS握手有一个会话恢复机制。验证通过后,可以直接使用之前的会话密钥,减少握手往返次数。在加载之前,当服务器返回响应时,有几种情况,例如:服务器负载过重,服务器宕机,无法及时或快速响应请求,服务器距离太远或交叉运营商导致高延迟。这里的优化其实和连接建立部分的优化是共享的,只是简单的建立一个普通的连接消耗的资源较少,所以这里会解释的比较全面。增加带宽:但在大多数情况下,服务器带宽并不是影响延迟的主要因素。智能DNS解析:根据客户端的IP地址,将域名解析到最近或非跨运营商服务器的IP地址,解决地理位置和跨运营商延迟问题。CDN:根据节点服务器的地理位置、负载情况和资源匹配情况,采用一定的分析方法从遍布各地的节点服务器中找到最合适的静态资源服务器。负载均衡:利用DNS负载均衡、IP负载均衡、反向代理负载均衡等方式,选择最合适的服务器来处理来自一堆服务器(职责相同的集群)或一组服务器(分布式职责)的请求。这些技术可以相互结合。比如CDN会使用DNS智能解析和负载均衡。使用跳转重定向方式的会再次进行DNS解析和握手,部分优化其实是在域名的DNS解析部分完成的。开始加载,服务器终于可以愉快的返回数据了。HTTP1.1流程:虽然HTTP1.1有长连接,一个TCP连接可以请求多个资源,但是这些资源的下载是串行的,比如使用这个TCP通道请求1.css,2.css,1.js,只有在上一次传输成功并完全完成后,才会进行下一次传输。虽然浏览器一般会请求建立多个TCP连接(多个端口向服务器的一个端口请求资源,新的TCP连接的建立会进行一次新的握手),并行请求页面资源以加快整体下载速度,但是对于每个域名的并发连接数是有限制的(保护服务器负载,并且受主机端口限制),所以我们还是需要做一些优化。优化:减少页面需要发起的请求总数,比如我们日常使用的代码合并,sprite图片(spriteimages/Sprite合并小图标),图片转base64写入其他文件,避免清空imgsrc属性等切分数据,让首屏数据先加载等补充:增加资源分发域名,部署在不同域名下,“突破”浏览器的并行连接限制(合并有DNS部分,不容易太分散,连接太多会共享带宽,移动端解析更慢)。HTTP2因为HTTP2提供了多路复用的功能,基于二进制数据帧和流的传输,通过一个TCP连接实现数据分散、无序、并行传输成为现实,即我们所有的资源都可以传输通过TCP连接。连接非阻塞并行传输。所以,我们做的减少HTTP1.1的请求数,增加资源分发域名的合并优化,都变成了无效的优化,可以舍弃了。同时,由于文件不需要合并,我们在更新文件时不需要修改单个开发模块来更新所有(合并文件)模块。一般来说,加载是一个很简单的过程,客户端收到服务器传输返回的响应。针对传输优化的数据大小越小,传输速度越快,延迟越低。更小:Gzip:启用Gzip压缩响应体,可以减少70%的数据量。减少cookies:移除不需要的cookies,并设置合适的过期时间。放弃cookies:对于静态文件请求,我们不需要cookies,即HTTP1.1中提到的,它们分布在其他域名下,子域应该设置合理的domian(cookie作用域)。头部压缩:HTTP2还提供了头部压缩功能,即通过一些双方共享的字典,将头部信息(状态行、请求/响应头部)“映射”成更短的数据。图像:使用适当的图像尺寸和图像格式以节省尺寸。缓存:最小的情况下,当然不接受数据传输,使用本地缓存。一般使用服务器上次返回的响应头域来进行控制。强缓存:强缓存不会向服务器发送请求。expires:http1.0字段,由服务器时间标识。Cache-Control:max-age=seconds,使用相对于请求的时间,不要超过这个时间,直接使用缓存。还有其他值。协商缓存:Last-Modified/If-Modified-Since:资源***修改时间,单位秒。浏览器客户端发送If-Modified-Since字段,服务器响应Last-Modified字段。ETag/If-None-Match:资源的标识,客户端发送If-None-Match字段,服务器响应ETag字段,两者比较决定是否返回缓存重定向或其他。这个标识符只比较内容,不关心资源时间。合理拆分页面资源,如外部js、css,可以独立于html缓存。关闭TCP资源下载完成后,需要关闭TCP连接。这部分没有什么可以优化的。过程:TCP客户端发送一个FIN=1(结束标志)和随机数seq=a,关闭客户端到服务器的数据传输。服务端收到这个关闭请求,返回ACK=1,ack=a+1,此时可能服务端之前的数据还没有传完。数据传输完成后,服务端发送一个FIN=1和随机数seq=b给客户端。客户端返回ACK=1,ack=b+1,等待一段时间确保服务器没有返回没有收到确认报文的重传请求,然后关闭连接。服务器收到确认消息后,验证并关闭连接。总结HTTP2真的很容易使用。明智地使用缓存。页面解析和渲染上面提到的资源加载发生在页面解析过程中。那么浏览器页面解析渲染的过程是怎样的呢?简单来说,流程就是:处理HTML标签,构建DOM树。处理CSS标记并构建CSSOM树。将DOM树和CSSOM树合并成渲染树(不需要渲染的dom会被忽略)。根据渲染树的布局,计算出每个节点的几何信息。在屏幕上绘制单个节点。中间遇到各种资源,就会下载资源。资源下载问题:下载css时会阻塞渲染(带media属性的除外)。当遇到script标签时,DOM构建停止,将控制权交给js,直到脚本(下载)执行完毕。这时候浏览器优化的话一般会去下载其他资源,但是不会去解析。js中如果有对CSSOM的操作,会先确保CSSOM已经下载构建。下载图片资源不会造成阻塞。重绘重排导致渲染树重新生成:重排(reflow):会重新计算布局,通常是由于元素的结构、增删、位置、大小发生变化,如:img下载后成功,替换填充页面的img元素,导致尺寸变化;还有读取js属性值引起的,比如读取offset、scroll、cilent、getComputedStyle等信息。重绘:简单的外观变化都会引起重绘,比如颜色变化等,重新排列必须重绘。优化dom简化dom结构,减少构建DOM树和渲染树的成本,减少页面元素的数量,比如使用列表和表格进行数据分页,简单的表格不要使用复杂的第三方组件。js将js脚本标签放在页面body底部,减少对其他进程的阻塞。延迟执行:对不修改页面的外链脚本使用defer属性,这样脚本的并行下载就不会被阻塞,下载完成后不会立即执行,而是在所有元素都执行完毕后执行解析。减少并合并不必要的dom相关操作,例如使用DocumentFragment、修改classname代替各种样式等,减少重绘重排的触发。css将css放在头部,提前加载,防止html渲染后重新组合css导致页面闪烁。降低css层级和css选择器的复杂度,提高解析速度,虽然浏览器进行了优化。使用更高效的css样式,例如flex而不是float。开启复合层,比如使用3D变换、不透明度等,这样元素及其子元素就不会造成外部重排,但也有坑。合理使用脱离文档流的样式,减少对外部重排的影响,比如absolute。文件数量减少下载文件数量,使用图片延迟加载、js按需加载等方式,也可以节省用户流量,甚至可以使用storage来缓存js、css文件。拆分页面资源,首屏数据优先加载等5.其他优化措施我们还可以采取一些与延迟和渲染无关的优化措施:使用PWA让用户在没有获取数据的情况下也能看到页面.在页面上存储一些ajax请求数据。加载进度、骨架图、占位符图像和类似的措施让用户感觉更好。及时更新升级服务器,优化措施视服务器支持情况而定。
