URL解析全过程

  如果我们需要知道一次详细的url解析过程,需要了解一些基础性的知识和概念,如什么是RUL,什么是DNS?下面分别来一一进行介绍。

  URL(Uniform Resource Locator): 统一资源定位符,URL是使用浏览器访问web页面时需要输入的网页地址。如:https://www.baidu.com/就是URL。也被称为“网址”。

  我们首先看一下https://www.bilibili.com/?spm_id_from=333.851.b_7265706f7274466972737431.1这个网址的组成部分

    1.传输协议:它主要是用于传输客户端和服务器端通信的信息。

      http协议(超文本传输协议):它是一个基于请求/响应模式的无状态协议。支持除文本外的富媒体资源,如图片,视频等;

         https它是http+ssl(加密传输):https就是在http下加了SSL层从而来保护交换数据的隐私和完整。一般来说它可以通过证书等相关信息确认网站的真实性,建立加密的信息通道,保证数据内容的完整。一般用于支付类网站如:https://www.alipay.com/;

      ftp协议(文件上传下载协议):一般用于客户端和服务器端文件的直接传输。

    2. 服务器域名:用户访问网页时,DNS服务器会根据用户提供的域名查到相应的IP地址。上面使用的是:bilibili.com

    3. 端口号:端口是服务器用于内外部通信的通道,一般是0~65535之间。它可以用来区分同一台服务器上不同项目,当用户访问服务器时必须从要求的端口访问才能正常打开网页。上面用的是端口号是80。自己写地址时,不加端口号,浏览器会按照默认端口号自动补充上。

    4. 问号传参:客户端想把信息传递给服务器,可以基于问号传参进行处理。还可以用于客户端一个页面跳转到另一个页面。在新的页面中获取到旧页面的某些内容,也是可以基于问号传参来实现。也可以用于组件传参。

  与URL相比,还有URI(Uniform Resource Identifier) 统一资源标识符。URI就是由某个协议方案表示的资源的定位标识符。协议 方案是指访问资源所使用的协议类型名称。

  一个完整的URL解析要分为以下几步:1.url解析 2.缓存检查 3. DNS解析 4. 建立TCP链接 5. 发送HTTP请求 服务器处理 6. 关闭TCP连接通道 7. 客户端渲染。

  一、URL解析 

  在一个完整的URL解析中我们需要了解什么是http,怎么进行编码解码以有什么是HSTS。

  1、HTTP协议

  什么是HTTP协议?HTTP协议是Hyper Text Transfer Protocol(超文本传输协议)的缩写,它基于TCP/IP通信协议来传递数据,浏览器作为HTTP客户端通过URL向HTTP服务端即WEB服务器发送所有请求。Web服务器根据接收到的请求后,向客户端发送响应信息。

  如果说HTTP是因特网的信使,那么HTTP报文就是它传递的信息,所有客户端和服务器端传输的信息统称为“报文”。如果我们每完成一次请求和响应那就可以称之为一次HTTP事务了。

  一个HTTP请求报文由请求行(request line)、请求头部(header)、空行和请求数据4个部分组成。

  请求行它是请求报文的起始行,包含了一个方法和一个请求URL,这个方法描述了服务器应该执行的操作,请求URL描述了要对哪个资源执行这个方法。请求行中还包含HTTP的版本,用来告知服务器,客户端使用的是哪种HTTP。请求行分为三个部分:请求方法、请求地址URL和HTTP协议版本,它们之间用空格分割。

   请求方法:请求的起始行以方法作为开始,方法用来告诉服务器要做什么,常见的请求方法有以下几种:GET系列:GET / DELETE / HEAD / OPTIONS,POST系列:POST / PUT 下面分别进行介绍:

  GET系列 : 一般认为是从服务器获取到信息,当然也可以把客户端的信息传递给服务器,给的少,拿的多。

    GET: GET是最常用的方法。通常用于请求服务器发送某个资源。GET方法要求服务器将URL定位的资源放在响应报文的数据部分,发送给客户端。使用GET方法时,请求参数和对应的值附加在URL后面,利用问号‘?’代表URL的结尾与请求参数的开始,传递参数长度受限制。

    DELETE: DELETE方法所做的事情就是请服务器删除请求URL所指定的资源。一般应用于想删除服务器上的文件或者是一些大量的信息。

    HEAD: 只需要获取响应头的信息就可,响应主体信息不接受。这就允许客户端在未获取实际资源的情况下,对资源的首部进行检査。

    OPTIONS: 试探性请求,这个请求用于校验客户端和服务器端是否正常连接。

  POST系列:一般认为是给服务器推送信息,给的多,拿的少。

    POST: POST方法将请求参数封装在HTTP请求数据中,以名称/值的形式出现,可以传输大量数据,这样POST方式对传送的数据大小没有限制,而且也不会显示在URL中.POST方式大多用于页面的表单中.

    PUT: PUT方法会向服务器写入文档。一般用于给服务器传递文件或者是大的数控,如文本编辑器编辑的内容。

  GET与POST的区别:

    GET传递的服务器的信息一般都是基于url地址问号传参来进行实现,如:./data.json?x=1&name=davina&xxx=xxx......。地址中‘?’之后的部分就是通过GET发送的请求数据,各个数据之间用‘&’符号隔开。

    不足:GET方式不适合传送私密数据。不同浏览器对地址字符的限制也不尽相同。一般最多只能识别1024个字符,所以如果需要传送大量数据的时候,也不适合使用GET方式。

    POST: 允许客户端给服务器提供信息较多,POST传递给服务器的信息一般基于请求主体来实现,客户端还可以基于设置请求头,把一些简要的信息传递给服务器。

    区别:

    GET传递给服务器的信息小于POST.因为不同浏览器对地址字符的限制也不尽相同。一般最多只能识别1024个字符,超出浏览器限制的部分,内容会被自动裁切掉,POST请求理论上是没有长度限制(请求主体没有设置大小限制),但在真实的项目中为了保证数据传输高效,我们一般都会手动做限制。

    安全问题:POST相对GET安全一些,项目中涉及安全信息的传输都是要用POST。主要原因是:get基于url传数据,但容易被url劫持掉,这样不安全,post相对来说安全,但也不是绝对安全。所以对于重要信息传输也需要进行手动加密处理。

    缓存问题:浏览器会在处理GET请求时,如果两次请求的地址后面参数一致,浏览器会自己设置数据缓存(当然这个缓存我们不想要)想要不走浏览器的缓存,我们需要保证每次请求的url都不完全一致:每次请求,问号传参后加上随机数或者时间戳'如:'./data.json?x=1&name=davina&_='+Math.random()。

  请求头部为请求报文添加了附加信息,它是HTTP报文要素之一。HTTP首部字段是由首部字段名和字段值构成(名/值),中间用冒号“:” 分隔。每行一对,名和值之间使用冒号分隔。请求头部的最后会有一个空行,表示请求头部结束。

  接下来为请求数据,它可以分为五类:通用首部、请求首部、响应首部、实体首部和扩展首部。

  通用首部:通用首部可以在客户端、服务器和其他应用程序之间提供一些非常有用的通用功能,下面是一些常见的通用首部信息:

    首部:Connection      描述:允许客户端和服务器指定与请求/响应连接有关的选项,如Connection:Keep-Alive

    首部:Date                 描述:提供日期和时间标志,说明报文是什么时间创建的

    首部:MIME-Version  描述:给出了发送端使用的MIME版本

    请求首部:只在请求报文中有意义的首部。用于说明是谁或什么在发送请求、请求源自何处等等信息。常见的有以下:

    首部:From                描述:提供了客端用户的地址

    首部:Host                 描述:给出了接收请求的服务器的主机号和端口名

    首部:User-Agent      描述:将发起请求的应用程序名称告知服务器

  响应首部:响应报文有自己的响应首部集。响应首部为客户端提供了一些额外信息

  实体首部:实体首部提供了有关实体及其内容的大量信息,实体首部可以告知报文的接收者它在对什么进行处理。

  扩展首部:HTTP首部字段是可以自行扩展的。所以在Web服务器和浏览器的应用上,会出现各种非标准的首部字段。 

  2、编码和解码

  当客户端和服务器端进行通信时如果出现了中文则需要进行编码和解码。JS的编码和解码有以下三种方式:

  encodeURI()/decodeRUI(): 这两个函数把字符串作为URI进行编码/解码,实际上encodeURI()函数只把参数中的空格编码为%20,汉字进行编码,其余特殊字符不会转换。

    <script>
        let str = "www.baidu.com/davina /微信";
        console.log(encodeURI(str)); //www.baidu.com/davina%20/%E5%BE%AE%E4%BF%A1

        let str1 = "www.baidu.com/davina /微信/ac1";
        console.log(encodeURI(str1)); //www.baidu.com/davina%20/%E5%BE%AE%E4%BF%A1/ac1
    </script>

  encodeURIComponent()/decodeURIComponent(): 这两个函数可把字符串作为URI组件进行编码/解码。由于这个方法对:/都进行了编码,所以不能用它来对网址进行编码,而适合对URI中的参数进行编码/解码。

    <script>
      let uri = "https://www.davina/com/from=http://wwws.baidu.com";
      console.log(encodeURIComponent(uri)); //https%3A%2F%2Fwww.davina%2Fcom%2Ffrom%3Dhttp%3A%2F%2Fwwws.baidu.com

      //所以一般我们的用法是:
      let newUri = `https://www.davina/com/from=${encodeURIComponent(
        "https://www.baidu.com"
      )}`;
      console.log(newUri); //https://www.davina/com/from=https%3A%2F%2Fwww.baidu.com
    </script>

  escape()/unescape(): 函数对字符串进行编码/解码,将字符的unicode编码转化为16进制序列。它也是可以解码中文的。用于服务端与服务端传输多。它不对/进行编码。

    <script>
      let uri = "https://www.davina/com/from=http://wwws.baidu.com";
      console.log(escape(uri)); //https%3A//www.davina/com/from%3Dhttp%3A//wwws.baidu.com
    </script>

  二、缓存检查

  所谓的浏览器缓存就是浏览器将用户请求过的资源存储到本地电脑。当浏览器再次访问时就可以直接从本地进行加载,不需要去服务端进行请求。它减少不必要的数据传输,减少服务器负担担升网站性能,提高了客户端网页的打开速度。一般分为强缓存和协议缓存。

  浏览器对于强缓存的处理是根据第一次请求资源时返回的响应头来确定的,它不会再向服务器发送请求,直接从缓存中读取资源。在chrome控制台中我们可以看到有Expires或者是cache-Control它们都是服务器设置并且是基于响应头信息返回给客户端的信息。是用来指定资源到期的时间,缓存过期时间。如果二者同时存在则cache-Control的优先级高于Expires。是否走缓存http的状态码都是200。所以对于强缓存来说,当客户端已经缓存信息而服务器资源文件进行更新这时用户就不能及时获取到服务器最新的资源信息了。

  而协商缓存这种缓存机制客户端需要和服务器端进行协商,它是在强缓存失效的情况下才触发的一种机制。在了解协商缓存前我们先要了解以下几个名词:

  Last-Modify/If-Modify-Since:浏览器第一次请求资源文件时,服务器返回的header中会加上Last-Modify它是一个时间标识,标识着这个资源最后的修改时间,当浏览器再次请求该资源时request请求头中会包含If-Modify-Since这个值为缓存之前返回的Last-Modify。服务器收到If-Modify-Since后会根据资源的最后修改时间判断是否命中缓存

  Etag: web服务器响应请求时,它会告诉浏览器当前资源在服务器的唯一标识。Etag要优于Last-Modified。Last-Modified的时间单位是秒,如果某个文件在1秒中改变了很多次,那么它的Last-Modified其实并没有体现出来修改,但是Etag每次都会改变确保了精准度。但是Etag在性能上要逊于Last-Modified的,服务器校验会优先考虑Etag。

  If-None-Match: 当资源过期时发现资源有Etag声明,则再次向web服务器请求时带上If-None-Match(Etag值)。web服务器收到请求后发现有If-None-Match则与被请求资源的相应校验串进行比较决定是否命中协商缓存。

  所以由上可以看出浏览器缓存过程如下:

  1、当我们向服务器第一次发送请求时,因为没有缓存所以直接从服务器上获取信息,服务器返回200,并把response header及请求的返回时间一并缓存,客户端拿到内容后把信息和标识缓存到本地;

  2、当我们再次发送请求浏览器它会首先检测本地是否有缓存或者是缓存是否过期,如果没有过期则直接基于缓存信息进行渲染,如果时间过期则向服务器发送header带有If-none-Match和If-Modified-Since的请求;

  3、服务器接收到请求后优先根据Etag的值判断被请求的文件有没有做修改,Etag值一致没有修改,命中协商缓存返回304如果不一致则说明有修改直接返回新的资源文件带上新的Etag值并返回200;

  4、如果服务器收到的请求中没有Etag则将If-Modified-Since和被请求文件的最后修改时间做比较,如一致则命中协商缓存返回304,不一致则返回新的last-Modified和文件并返回200。 

  三、DNS解析

   域名是一串用点分隔的数字组成的internet上某一台计算机或者计算机组的名称。简单来说它可以理解成为通向某个网站的路。由于IP地址不方便记忆所以人们设计出了域名,并通过DNS将域名和IP地址相互映射,这样我们可以更加方便的快速的访问互联网了。只有顶级的域名是需要花钱进行购买的。

  DNS(Domain Name System): DNS服务它是一种提供域名到ip地址之间的解析服务。计算机既可以被赋予ip地址,也能被赋予主机名和域名。它解析有以下几步组成:

  查找本地是否有缓存,如果有则本地解析,本地解析的话浏览器先检查自身中有没有缓存,如果有,解析结束。如果没有,则 查看c盘hosts文件,如果有浏览器会首先使用这个ip地址。如果本地hosts文件里没有,则请求本地DNS解析器,解析这个过程。

  如果本地没有缓存,从根域名服务器,顶级域名服务器,权威域名服务器上进行查找,把查找到的结果返回给本地DNS解析器,返回给客户端。典型一次DNS解析需要花费20~120毫秒,这样就很浪费时间。

  所以我们需要对DNS解析进行优化,进行DNS预解析(DNS Prefetch)。也就是说根据浏览器定义的规则利用link的异步加载在GUI线程渲染页面的同时去解析DNS并把解析结果缓存到系统缓存,当GUI渲染到某部分需要请求外部资源这时我们已经解析好了这样可以缩短DNS解析时间来提高网站的访问速度。

  当代产品的开发资源一般都是部署到不同服务器上,尤其对于大型项目而言,可以分为web资源服务器,图片资源服务器,数据接口服务器,第三方服务器......。这样分开部署可以对服务器资源进行分配,在每台服务器并发有上限的情况下,这样部署可以提高并发,每一个源下可以同时允许的HTTP并发数是6~7个,这样分开部署有助于提高页面的渲染速度。

  DNS预解析的具体方法如下:

 //用meta信息来告知浏览器, 当前页面要做DNS预解析
<meta http-equiv="x-dns-prefetch-control" content="on">
//在页面header中使用link标签来强制对DNS预解析:
<link rel="dns-prefetch" href="//www.xxx.com">

   四、建立TCP通道

  浏览器通过DNS获取到web服务器真的IP地址后,便向服务器发起TCP连接请求,通过TCP的三次握手建立好连接后,浏览器便可以将http请求数据通过发送给服务器了。那什么是TCP?为什么需要握手?为什么握手不是两次,或者是4次而是三次?

     TCP是可靠通信协议,接收方收到的是完整,有序,无差错的数据。可靠性高,应用于传输大量数据对可靠性要求高的场合。与TCP相对的是UDP它是不可靠通信协议,接收方接收到的数据可能存在部分的丢失,顺序也不一定能保证,但是它是的传输速度快。

  TCP和UDP协议它们都是基于同样的互联网基础设施,基于IP协议来实现的,互联网基础设施中对于数据包的发送过程是会发生丢包现象的而TCP协议为了实现可靠传输引入了序号(sequence number)

和确认号(acknowledgement number)确保了通信双方会判断自己发送的数据包对方是否收到,如果没有收到则重发,而UDP则做不到。

  发送方要发送数据包时,同时送一个序号,那么接收方收到这个数据包后,就可以回复一个确认号,告诉发送方“我已经收到了你的数据包,你可以发送下一个数据包,序号从某某开始”。

  为什么是三次而不是两次?为了实现可靠传输,发送方和接收方始终需要同步序列编号(Synchronize Sequence Numbers)。 需要注意的是, 序号并不是从 0 开始的, 而是由发送方随机选择的。

初始序列号 ( Initial Sequence Number, ISN )开始 。 由于 TCP 是一个双向通信协议, 通信双方都有能力发送信息, 并接收响应。 因此, 通信双方都需要随机产生一个初始的序列号, 并且把这个起始值告诉对方。

  所以TCP的三次握手如下:

  第一次握手:建立连接时,客户端发送syn包(syn=x)到服务器,并进入SYN_SENT(Synchronize Sequence Numbers)状态,等待服务器确认。(白话文:你好啊,服务器,我要发数据啦,先出一个题目考一下你哟,对一下暗号。)

  第二次握手:服务器收到syn包,必须确认客户的SYN(ack=x+1),同时自己也发送一个SYN包(syn=y),即SYN+ACK包,此时服务器进入SYN_RECV状态。(白话文:你好啊,浏览器我准备好啦,你可以发呢,题目已经做好了我也出题考考你啦)

  第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=y+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。(白话文:你好啊,服务器,我马上要发数据啦,你准备接收吧,做出题目返回给服务器)

  五、服务器处理请求,返回响应结果,数据传输

  服务器接收到请求的数据后,会根据端口号请求资源的路径名称找到资源文件并读取文件中的内容,把内容返回。所有经过传输协议,服务器返回给客户端的内容,都被成为响应报文。它也有三部分组件:HTTP状态码,响应头和响应主体。

  状态码:HTTP状态码负责表示客户端HTTP请求的返回结果、标记服务器端的处理是否正常、通知出现的错误等工作。HTTP状态码被分成了五大类,不同的类型代表不同类别的状态码。

  【1XX】: Informational(信息性状态码) 表示接收的请求正在处理,一般看不到

  【2XX】:Success(成功状态码) 表示请求正常处理完毕。代表:200(成功),204(对于某些请求,服务器不想处理返回空和这个码)

       【3XX】:Redirection(重定向状态码) 表示需要进行附加操作以完成请求。如:301(永久转移),307(临时重定向Temporary Redirect =>主要用于:服务器的负载均衡)

  【4XX】 :Client Error(客户端错误状态码) 表示服务器无法处理请求。如:400(参数错误),404(请求地址错误)

  【5XX】 :Server Error(服务器错误状态码) 表示服务器处理请求出错。如:500(未知服务器错误),503(服务器超负荷)

  六、关闭TCP连接通道(四次挥手)

  第一次挥手: 首先从客户端开始发出连接释放报文,并且停止发送数据,这客户端进入到终止等待1状态。(白话文:你好啊服务器,我请求报文发送完啦,你准备关闭吧)

  第二次挥手:从服务器到客户端 。服务器收到连接释放报文,发出确认报文且带上自己的序列号,这时服务器端进入了关闭等待状态。(白话文:你好啊浏览器,我接收完请求报文准备关闭啦,你也准备吧)

  这时TCP服务器要通知高层的应用进程,客户端向服务器释放,这时服务器处于半关闭状态。如果服务器还有数据要发送,客户端依然要接受。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。

  客户端收到服务器的确认请求后,此时,客户端就进入FIN-WAIT-2状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后的数据

  第三次挥手:服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,由于在半关闭状态,服务器很可能又发送了一些数据,服务器就进入了最后确认状态,等待客户端的确认。(白话文:你好啊浏览器,我响应报文发送完啦,你准备关闭吧)

  第四次挥手:客户端收到服务器释放报文后,发出确认此时,客户端就进入了时间等待状态。因为此时TCP连接还没有释放,必须经过一段时间2∗∗MSL(最长报文段寿命),因为网络是不可靠的,有可以最后一个ACK丢失。所以时间状态就是用来重发可能丢失的ACK报文的。当客户端撤销相应的TCB后,才进入CLOSED状态。(白话文:你好啊服务器,我响应报文接收完毕了,我准备关闭了,你也准备吧)
  服务器只要收到了客户端发出的确认,立即进入CLOSED状态。同样,撤销TCB后,就结束了这次的TCP连接。服务器结束TCP连接的时间要比客户端早一些。

  所以我们可以看到当服务器端收到客户端的SYN连接请求报文后,可以直接的发送请求连接和确认连接,但在关闭的时候,服务器收么到关闭信息后,很可能不会立即关闭,它需要时间来反应,只能先回复一个确认,只有当服务器端所有的报文都发送完后,服务器端才能发送希望断开连接。所以需要挥手需要四步来完成。

  六、客户端渲染

  不同的浏览器内核不同,所以渲染过程不太一样。但大体上浏览器渲染进程包含解析HTML文件和CSS文件、加载图片资源文件,执行解析js文件脚本代码等内容。整个过程浏览器会开启多个线程协作完成。

  浏览器是专门用来访问和浏览网页的客户端软件。在深入了解浏览器的渲染机制前提下,我们首先要知道几个知识点,如什么是线程?什么是进程?浏览器是由什么组成的?

  进程:操作系统分配的占有CPU资源的最小单位,它有独立的地址空间。

  线程:安排CPU执行的最小单位。同一个进程下可以有多个线程,它们是共享进程的地址空间。可以简单来看,计算机工厂,线程是大车间,而线程是大车间是不同的部门。需要注意的是这些部门是没有层级关系的,它们之间相互工作。

  浏览器是多进程的,浏览器的渲染进程是多线程的。浏览器常用的线程有:GUI渲染线程,JavaScript引擎线程,定时器触发线程,事件触发线程,异步请求线程等等。

  浏览器的组成如下图所示:

  一、渲染流程

  从资源的下载到最终的页面展现,它的中间依次经过以下几部分:

  webkit引擎渲染的详细流程如下图所示:

  1. 构建DOM TREE

    解析html文档,生成DOM树。

   a. 解析html。html语法树分为两个部分:词法解析和语法解析。

      词法解析按照词法规则来进行,将html文本分割成大量的标记(Token)

      语法解析按照语法规则匹配Token生成语法树,但浏览器内核中对html页面真正的内部表示并不是语法树,而是w3c规范的DOM.DOM也是树形的结构,它的节点基本和html语法树节点一一对应,所以在语法解析过程中,最后直接生成了DOM树。

   b. 解析css。页面中所有的CSS由CSS样式表(CSSStyleSheet)集合而成。一个 CSS 样式表包含了一组表示规则的 CSSRule 对象。每一条CSSRule则由选择器部分和声明部分构成,而声明部分是CSS属性和值的Key-Value集合。css解析完毕后会进行CSSRule的匹配过程,寻找满足每条CSS规则Selector部分的HTML元素,然后将其Declaration部分应用于该元素。实际规则匹配过程会考虑到默认和继承的CSS属性、规则的优先级等因素。

   c. 解析javascript。它一般是由单独的脚本引擎来解析执行,通常是动态的改变DOM树。

  2. 构建RENDER TREE

  渲染树(Render Tree)它可以和DOM树一一对应,二者在内核中同时存在,作用不同。DOM树是html文档对象表示,同时也是JS操作html的接口,Render Tree是DOM Tree树的排版表示,用以计算可视DOM节点的布局信息和后续阶段的绘制显示。要注意一点,并不是所有的DOM节点都可视,也就是说并不是所有DOM Tree节点都会对应生成一个Render Tree节点(如head标签)。同时,DOM Tree可视节点的CSS 样式就是其对应Render Tree节点的样式。总结来说就是用dom tree和cssom tree构造render tree。

  3. 布局RENDER TREE(layout)

  布局RENDER TREE首先要计算布局。布局是安排和计算页面中每个元素大小位置的过程。这个过程就是通过Render Tree中渲染的对象信息,计算出每一个渲染对象的位置和尺寸,将它放在浏览器窗口正确的。

  4. 绘制RENDER TREE(paint)

    Paint模块负责将Render Tree映射成可视的图形,它会遍历Render Tree调用每个Render节点的绘制方法将其内容显示在一块画布或者位图上,并最终呈现在浏览器应用窗口中成为用户看到的实际页面。

    注意:reflow & repaint

      有时文档布局完成后我们会对dom进行修改,这时可能会重新进行布局,可以称其为回流(reflow)或者是重排(relayout)。由于html主要使用的是流式布局,如果页面中的一个元素尺寸发生了变化,那后续的元素位置都要跟着变化,要重新进行布局。触发回流的方式:窗口尺寸被修改,发生滚动等等。

      每个页面至少需要一次回流,那就是在页面第一次加载的时候。回流发生在Render Tree上。我们通常所说的脱离文档流就是指脱离Render Tree.

      重绘是指当与视觉相关的样式属性值被更新时会触发的绘制过程(只是影响元素的外观,风格不会影响布局),在绘制过程中要重新计算元素的视觉信息,使元素呈现新的外观。

      回流一定会引起重绘,重绘不一定会引起回流。

      虽然回流是必不可少的一步,无法避免,但我们要避免多次的回流与重绘。有以下方法:

        a.不要一条条的修改dom样式,样式集中改变;

        b.对一个元素进行复杂操作时可以先隐藏,操作完再显示;

        c.需要经常获取那些引起回流的属性值进,要缓存到变量中;

        d.不要使用table布局,分离读写操作;

        e.放弃传统操作DOM开始使用vue或者是react,我们自己不操作DOM,只操作数据,让框架帮我们根据数据渲染视图等

 

   当服务器返回一个html文件给浏览器,浏览器接受到的是一些字节数据,它会根据http响应中的编码方式(通常是utf-8)解析字节数据,得到代码字符串,然后按照w3c的规则进行字符解析,生成对应的tokens,接着浏览器再使用这些tokens创建对象,转换为浏览器内核可以识别和渲染的DOM节点。这时html解析器就会从自上而下遍历这些节点,节点如果是普通节点html解析器就将其到dom树中,如果是css代码就交把css解析器,如果节点是js代码html就会交给js解析器。节点最后解析为对应的dom tree 和 cssom tree。把dom tree和cssom tree结构合在一起,生成有结构的render tree。最后浏览器按照render tree在页面中进行渲染和解析呈现在浏览器应用窗口中成为用户看到的实际页面。

  正常情况下我们可以看到js会阻碍GUI的渲染,所以一般js放在页面的尾部,确保DOM Tree生成完才会去加载js。我们也可以用defer或者是async异步管控js的请求

  defer:属性表示延迟执行引入js,js加载时html并未停止解析,这两个过程是并行的。整个 document 解析完毕且 defer-script 也加载完成之后(这两件事情的顺序无关),会执行所有由 defer-script 加载的 JavaScript 代码,再触发 DOMContentLoaded事件(初始的 HTML 文档被完全加载和解析完成之后触发,无需等待样式表图像和子框架的完成加载) 

  async属性表示异步执行引入js,与 defer 的区别在于,如果已经加载好,就会开始执行,无论此刻是 html解析阶段还是 DOMContentLoaded 触发之后。需要注意的是,这种方式加载的js依然会阻塞 load 事件。换句话说,async-script 可能在 DOMContentLoaded 触发之前或之后执行,但一定在 load 触发之前执行。多个 async-script 的执行顺序是不确定的,谁先加载完谁执行。

  总结:浏览器的渲染进程是多线程的。浏览器常用的线程有:GUI渲染线程,JavaScript引擎线程,定时器触发线程,事件触发线程,异步请求线程等等。

  GUI渲染线程:负责渲染浏览器界面,解析html,css,构造DOM Tree ,Render Tree,layout,print等等。

  JavaScript引擎线程:负责解析js代码,如v8引擎。它和GUI渲染线程是互相排斥的,当JS引擎执行时,GUI会被挂起,GUI更新会被保存在一个队列中等到js引擎空闲时执行,所以JS执行时间不能过长,不然会造成页面的渲染的不连贯。

  事件触发线程:处理DOM事件,归属于浏览器,用来控制事件循环,当相应的事件符合触发条件时,这个线程会把事件添加到等待队列的队尾进行处理

  定时器触发线程:setTimeout和setInterval所在的线程,它们是通过单独线程来计时并触发定时的。

  异步http请求线程:处理http请求

  渲染进程如下:

  打开一个浏览器可以看到任务管理器出现两个进程,一个是主线程,一个是打开Tab页的渲染进程。主进程收到用户请求,首先要获取页面的内容,将这个任务通过RenderHost接口传递给Render渲染进程,Render渲染渲染进程收到消息后,交给GUI渲染线程,开始渲染、GUI渲染线程接收到请求后,加载渲染页面,这其中有可能会需要主进程来获取资源,会有JS线程操作DOM等等。最后Render渲染进程将结果传递给主进程,主进程接收结果并绘制。

  所以说,当浏览器输入一个url后,首先要在客户端上进行url解析,在DNS服务器上进行DNS解析,建立tcp连接通道后浏览器向服务器发送http请求,服务器把结果返回给浏览器,返回要关闭tcp连接通道,然后在浏览器上把获取的结果进行渲染。浏览器内核拿到相应内容后,渲染开始,解析html,css分别建立DOM Tree和CSSOM Tree,随后这两个合并成Render Tree。然后布局Render Tree,绘制Render Tree,绘制页面像素信息。浏览器会将各层的信息发送给GPU,GPU会将各层合成,显示在屏幕上。渲染完成。

 

 

原文地址:https://www.cnblogs.com/davina123/p/12978114.html