浏览器输入URL后发生了什么?有哪三棵树?

78 阅读9分钟

一、主要分为六步:

  1. 合成URL
  2. DNS域名解析
  3. 建立TCP连接
  4. 发送HTTP请求,服务器处理请求,返回响应结果
  5. 关闭TCP连接,四次握手
  6. 浏览器渲染

二、具体介绍:

  1. 合成URL:

    用户输入URL,浏览器会根据用户输入的信息判断是搜索还是网址。===》1)如果是搜索内容则将搜索内容+默认搜索引擎合成新的URL;2)如果输入的内容符合URL规则,浏览器会根据URL协议在这段内容上加上协议合成合法的URL。

  2. DNS域名解析:

    客户端和浏览器,本地DNS之间的查询方式是递归查询;本地DNS服务器与跟域及其子域之间的查询方式是迭代查询。

    DNS服务器是高可用、高并发和分布式的,它是树状结构。

image.png 递归过程:

image.png 在客户端输入URL之后,会有一个递归查找的过程,从浏览器缓存中查找--》本地的hosts文件查找--》找本地DNS解析器缓存查找--》本地DNS服务器查找,这个过程任何一步找到了都会结束查找流程。

  1. 建立TCP连接:

    首先判断是否为HTTPS的。如果是则HTTPS其实是HTTP+SSL/TLS两部分组成,也就是在HTTP上又加了一层处理加密信息的模块。服务端和客户端的信息传输都会通过TLS进行机密,所以传输的数据都是加密后的数据。

    进行三次握手,建立TCP连接:

    1)第一次握手:建立连接。客户端向服务器发送连接请求报文段,将SYN位置为1,Sequence Number为x;然后,客户端进入SYN_SEND状态,等待服务器的确认;

    2) 第二次握手:服务器收到SYN报文段。服务器收到客户端的SYN报文段,需要对这个SYN报文段进行确认,设置Acknowledgment Number为x+1(Sequence Number+1);同时,服务器自己还要发送SYN请求信息,将SYN位置为1,Sequence Number为y;服务器端将上述所有信息放到一个报文段(即SYN+ACK报文段)中,一并发送给客户端,此时服务器进入SYN_RECV状态;

    3)第三次握手:客户端收到服务器的SYN+ACK报文段。然后将Acknowledgment Number设置为y+1,向服务器发送ACK报文段,这个报文段发送完毕以后,客户端和服务器端进入ESTABLISHED状态,完成TCP三次握手。

    (注意:ACK此标志表示应答域有效,1为有效、0为无效; SYN在连接建立时用来同步序号,SYN=1而ACK=0时表示这是一个请求连接报文;若对方同意建立连接,则相应报文中使SYN=1且ACK=1 FIN即为终结的意思,用来释放一个连接,FIN=1时表明此报文段的发送方数据已发送完毕,并要求释放连接。)

  2. 发送HTTP请求,服务器处理请求,返回响应结果:

    TCP连接建立后,浏览器就可以利用HTTP/HTTPS协议向服务器发送请求了。服务器接收到请求就解析请求头,如果头部有缓存相关信息,如:if-none-match与if-modified-since,则验证缓存是否有效。若有效则返回状态码为304,若无效则重新返回资源,状态码为200。

    这里有关一个HTTP缓存,常考考点:(此处有待进一步学习)

image.png

  1. 关闭TCP连接,四次分手:

    1) 第一次分手:主机1(可以使客户端,也可以是服务器端),设置Sequence Number和Acknowledgment Number,向主机2发送一个FIN报文段;此时,主机1进入FIN_WAIT_1状态;这表示主机1没有数据要发送给主机2了;

    2)第二次分手:主机2收到了主机1发送的FIN报文段,向主机1回一个ACK报文段,Acknowledgment Number为Sequence Number加1;主机1进入FIN_WAIT_2状态;主机2告诉主机1,我"同意"你的关闭请求;

    3)第三次分手:主机2向主机1发送FIN报文段,请求关闭连接,同时主机2进入LAST_ACK状态;

    4)第四次分手:主机1收到主机2发送的FIN报文段,向主机2发送ACK报文段,然后主机1进入TIME_WAIT状态;主机2收到主机1的ACK报文段以后,就关闭连接;此时,主机1等待2MSL后依然没有收到回复,则证明Server端已正常关闭,那好,主机1也可以关闭连接了。

  2. 浏览器渲染:

image.png 1)构建DOM树:

当浏览器接收到服务器响应来的HTML文档后,会遍历文档节点生成DOM树。注意:

  • DOM树在构建的过程中可能会被CSS和JS的加载而执行阻塞;
  • display:none的元素也会在DOM树中;
  • 注释也会在DOM树中;
  • script标签会在DOM树中。 无论是DOM还是CSSOM,都要经过以下过程:

image.png 当前节点的所有子节点都构建好后才会构建当前节点的下一个兄弟节点。

2)构建CSSOM树:

浏览器解析CSS文件并生成CSSOM,每个CSS文件都被分析成一个styleSheet对象,每个对象都包含CSS规则。CSS规则对象包含对应于CSS语法的选择器和声明对象以及其他对象。注意:

  • CSS解析可以与DOM解析同时进行;
  • CSS解析与script的执行互斥;
  • 在Webkit内核中进行了script执行优化,只有在JS访问CSS时才会发生互斥。

3)构建渲染树(Render Tree):

通过DOM树和CSS规则树,浏览器就可以通过它们构建渲染树了。浏览器会先从DOM树的跟节点开始遍历每个可见节点,然后对每个可见节点找到适配的CSS样式规则并应用。注意:

  • Render Tree和DOM Tree不完全对应;
  • display: none的元素不在Render Tree中;
  • visibility: hidden的元素在Render Tree中。 渲染树生成后还是没有办法渲染到屏幕上,渲染到屏幕需要得到各个节点的位置信息,这就需要布局(layout)的处理了。

4)渲染树布局(layout of the render tree):

布局阶段会从渲染树的跟节点开始遍历,由于渲染树的每个节点都是一个Render Object对象,包含宽高、位置、背景色等样式信息。所以浏览器就可以通过这些样式信息来确定每个节点对象在页面上的确切大小和位置,布局阶段的输出就是我们常说的盒子模型,它会精确的捕获每个元素在屏幕内的确切位置与大小。注意:

  • float元素、absolute元素、fixed元素会发生位置偏移;
  • 我们常说的脱离文档流,其实就是脱离Render Tree。

5)渲染树绘制(Painting the render tree):

在绘制阶段,浏览器会遍历渲染树,调用渲染器的paint()方法在屏幕上显示其内容。渲染树的绘制工作是由浏览器的UI后端组件完成的。

三、渲染阻塞:

script标签位置的重要性:

JS可以操作DOM来修改DOM结构,可以操作CSSOM来修改节点样式,这就导致了浏览器再遇到script标签时DOM构建将会暂停,直至脚本完成执行,之后继续构建DOM。若脚本是外部的会等待脚本下载完毕再继续解析文档。现在可以在script标签上增加属性deferasync。脚本解析会将脚本中改变DOM和CSS的地方分别解析出来,追加到DOM树和CSSOM规则树上。

每次去执行JS脚本都会严重阻塞DOM树的构建,若JS脚本还操作了CSSOM,并且正好这个CSSOM还没有下载和构建,浏览器甚至会延迟脚本执行和构建DOM,直至完成其CSSOM的下载和创建。所以script的位置很重要。JS阻塞了构建DOM树,也阻塞了其后的构建CSSOM规则树,整个解析进程必须等待JS的执行完成才能继续,这就是所谓的JS阻塞页面

由于CSSOM负责存储渲染信息,浏览器就必须保证在合成渲染树之前,CSSOM是完备的,这种完备是指所有的CSS(内联、内部和外部)都已经下载完,并解析完,只有CSSOM和DOM的解析完全结束,浏览器才会进入下一步的渲染,这就是CSS阻塞渲染

CSS阻塞渲染意味着,在CSSOM完备前,页面将一直处理白屏状态,这就是为什么样式放在head中,仅仅是为了更快的解析CSS,保证更快的首次渲染

四、回流和重绘(reflow和repaint):

HTML默认是流式布局的。但是JS和CSS会打破这种布局,改变DOM的外观样式以及大小位置。

引起reflow:

  • 页面第一次渲染(初始化)
  • DOM树变化(如:增删节点)
  • Render树变化(如:padding改变)
  • 浏览器窗口resize
  • 获取元素的某些属性。 浏览器为了获得正确的值也会提前触发回流,这样就使得浏览器的优化失效了,这些属性包括offsetLeft、offsetTop、offsetWidth、offsetHeight、 scrollTop/Left/Width/Height、clientTop/Left/Width/Height、调用了getComputedStyle()。

引起repaint:

  • reflow回流必定引起repaint重绘,重绘可以单独触发;
  • 背景色、颜色、字体改变(注意:字体大小发生改变时,会触发回流)。

减少reflow、repaint触发次数:

  • transform形变位移可以减少reflow;
  • 避免逐个修改节点样式,尽量一次性修改;
  • 使用documentFragment将需多次修改的DOM元素缓存,最后一次性append到真实DOM中渲染;
  • 可以将需要多次修改的DOM元素设置为display:none,操作完再显示。(因为隐藏元素不在render树内,因此修改隐藏元素不会触发回流重绘);
  • 避免多次读取某些属性;
  • 通过绝对位移将复杂的节点元素脱离文档流,行程新的Render Layer,降低回流成本。