美文网首页
全方位解析浏览器渲染原理

全方位解析浏览器渲染原理

作者: 廊桥梦醉 | 来源:发表于2023-08-05 15:59 被阅读0次

    文章思路有点不那么清晰,可结合文章(https://www.jianshu.com/p/f8ae03a295a2)一起看,有意外收获.

    进程和线程

    进程是操作系统资源分配的最小单位,进程包含线程

    线程是受进程管理的,浏览器采用的是多进程模式

    日常中我们使用浏览器是基于一个一个tab页来进行访问网站,如果说某一个tab页挂掉了,其实对于其他tab页是没有任何影响的,其实每一个tab页都是一个单独的进程

    他们之间相互独立,互不影响.

    浏览器中的进程:

    浏览器中的进程分为5个:

    1.浏览器进程:你可以理解浏览器进程时候一个统一的‘调度大师’去调度其他进程,比如我们在地址栏中输入url时,浏览器进程首先会调度网络进程,他可以做一些子进程管理以及一些存储的处理.

    2.渲染进程:这个进程对于我们来说是最重要的一个进程,每一个tab页都有一个独立的渲染进程,他的主要作用是渲染页面.

    3.网络进程:这个进程是控制对于一些静态资源的请求,它将资源请求完成之后交给渲染进程进行渲染.

    4.GPU进程:这个进程可以调用硬件进行渲染,从而实现渲染加速.比如translate3d等css3属性会骗取调用GPU进程从而开启硬件加速.

    5.插件进程:chrome中的插件也是一个独立的进程

    各个进程之间是相互独立,互不影响的.

    从输入url到页面显示之间究竟发生了什么

    网络资源层面

    首先我们先抛开浏览器对于资源的处理过程,先来看看一次正常的url输入在资源加载方面经历的生命周期.

    当我们在地址栏中输入一个url时,浏览器进程会监听到这次交互.紧接着它会分配一个渲染进程准备渲染页面,同时浏览器进程会调用网络进程加载资源.

    等网络进程加载完资源后会将资源交给渲染进程进行页面渲染.从进程的角度来说整体的加载流程就是这样.

    大的方面来说就是浏览器进程进行调度,网络进程加载完资源后交给渲染进程进行渲染加载的资源.

    接下来我们详细看看输入url之后的请求过程中究竟发生了哪些事情.

    网络七层协议

    我们可以将这七层归为下列四层:

    通常我们会将应用层、表示层、会话层统称为应用层,应用层的主要协议是http协议.

    传输层中我们浏览器中http协议是基于tcp去进行网络传输.(常见传输协议的有tcp还有udp)

    网络层中一般都是ip协议.

    当然在数据链路层和物理层都被称为物理层.

    我们先从7层协议来分析一下浏览器对于url加载的过程

    首先当我们输入url输入一个域名,浏览器会在磁盘/内存缓存中去查找请求的文件,查找是否命中缓存.如果命中缓存则直接会从缓存中那渠道对应的ip地址.

    如果命中缓存则会直接返回对用资源不会进入下面的步骤

    这里我们先忽略缓存带来的影响,这里涉及一个协商缓存强制缓存的知识点在下面的知识点中进行讲解.

    假设我们首次访问这个页面,此时并没有任何缓存:

    如果我们访问的这个域名没有被解析过,那么我们需要解析地址栏中输入的域名.解析域名主要依赖的是DNS协议,将域名解析为ip地址. ip地址才能找到对应的ip.

    dns你可以理解它为一个映射表,将域名和ip地址进行映射,其实就是一个分布式的数据库,通过域名查找到对应的ip地址.

    需要注意的是dns解析是基于udp协议而非tcp协议.

    这里又个小问题需要提一下,为什么dns解析时候基于udp而非tcp协议?

    我们的dns解析过程是一个服务器的查找过程.因为域名分为一级/二级...域名,所以每一级域名都会迭代去查询,如果它采用tcp协议的话,每经过一次域名查询,域名服务器都会经过三次握手.但是udp就不会,他会直接发包然后确认

    相较于udp,tcp是更加安全,可靠的(因为三次握手以及四次挥手)但是这也造成了它相较于udp消耗更多时间.

    udp常用的场景是视频或者直播中,对于我们来说dns解析中使用的udp更多的原因是因为udp的速度,当然即使丢包了,我们重新发送就可以了.

    tcp传输的过程称为分段传输,也就是会拆分为多个包,一个包一个包的进行发送得到响应之后就会发送下一个包.这样的方式无疑更加可靠和安全,但是在实效上并不如udp协议的实时(直接通信无需建立连接)

    此时会根据dns解析通过域名+端口号解析出对应的ip地址.

    我们拥有了ip地址之后,接下来我们就需要将利用ip进行寻找网页地址

    此时如果我们的请求地址时候https,在通过ip寻址之后会额外增加一步ssl协商保证数据的安全性

    当IP地址寻址成功后,浏览器知道了服务器的地址,此时并不会立即将数据发送过去,而是会进入一个排队的等待过程,比如一个域名下有多个请求,同一个域名在http1.1下最多只建立6个tcp链接,也就说同一个时间最多发送6个请求,他们首先会进入一个排队的等待时间.

    排队结束后开始发送请求.此时就需要通过tcp先进性创建链接通过三次五首,建立完成链接后传输数据.

    上边我们说过tcp是基于分段传输的,基于内容特别大的传输内容tcp会将数据包进行拆分成为多个数据包进行有序传输.

    在tcp的传输过程中如果传输中出现了丢包,那么tcp会进行重传.

    服务器接收到之后会按照顺序进行接收.

    tcp建立完链接之后,浏览器会通过http请求发送请求数据

    一次http请求包含
    请求行
    请求头
    请求体

    在http1.1中默认开启了connection:keep-alive,它的作用是在下次发送请求时在一定时间内可以复用上一次的tcp链接而不需要重新建立链接.(也就是在一定时间内保持相同域名tcp链接不断开).

    此时服务器接收到请求发送的数据,根据请求行、请求头、请求体进行解析,解析完成后返回相应行、响应头、响应体.

    注意:这里服务器返回状态码中有一些特殊的状态码

    301/302这两个状态码都是表示重定向,如果返回这两个任意一个,就会根据响应头中的location返回的域名重新进行之前操作(https://blog.csdn.net/qq_43968080/article/details/107355758)

    304状态码表示浏览器本次资源走缓存而不会重新请求下载资源.

    这个过程便是一个最基础的浏览器针对一个url访问网络请求的过程。

    以taobao.com为例让我们一探究竟

    上边说了那么多枯燥的理论,接下来让我们在实际中去体会一下。

    首先我们打开一个全新的浏览器tab页在地址栏输入taobao.com

    因为我是首次进入这个页面,所以并没有任何缓存。前边说到过浏览器进程首先会开启一个页面渲染进程,同时开启网络进程去请求。

    首先让我们打开chrome开发者工具:

    建议大家在新的无痕浏览页中去进行这些操作,我们排除掉DNS缓存以及任何浏览器缓存的干扰机制去看结果会更加纯粹。

    每一次重定向都会重新进行DNS解析以及TCP连接的建立是非常耗时的。所以在我们的真实项目中要尽量的避免进行资源重定向,如果有存在重定向的资源尽量还是将它直接替换成新的地址连接。

    接下来我们以第三次https://www.taobao.com/这次请求为例来分析一下一次请求(无任何缓存)的各个阶段:

    分析一次请求完整的瀑布图所代表的含义

    我们先来看看对应chrome中的瀑布图:

    Queueing 这个阶段表示排队阶段,浏览器在以下情况下对请求进行排队:

    有更高优先级的请求。

    已经为此打开了六个 TCP 连接,这是限制。仅适用于 HTTP/1.0 和 HTTP/1.1。

    浏览器在磁盘缓存中短暂分配空间

    Stalled 表示停滞不前,请求可能因上述排队中描述的任何原因而停止。(比如说链接开始后,会进行一些tcp连接的复用处理一些代理相关的逻辑)

    DNS Lookup 这一步就表示开始进行DNS解析,将我们的请求域解析为ip地址。

    Initial connection 这阶段表示我们进行tcp链接/重试和ssl协商共同耗费的时间。

    SSL 这一步就是当我们请求https域名时会进行ssl协商的耗时。

    request sent 表示请求开始发送

    watting for server代表 Time To First Byte。此时间包括 1 次往返延迟和服务器准备响应所用的时间。通俗来说就是当我们请求发送到接受到响应的第一个字节的时间。

    waitting for server 这一步通常可以粗略表示本次请求服务器(后台)从接受到请求然后返回响应结果处理的耗时。

    Content Download 就不必多说了,是我们下载本地响应的时间。

    同时对于chrome而言在http1.1下同一个域的最多支持并发6个TCP链接,注意这里是TCP链接而不是HTTP请求。

    我们用一个小例子来说明下,在同一个TCP连接里面,先发送A请求,然后等待服务器做出回应,收到后再发出B请求。管道机制则是允许浏览器同时发出A请求和B请求,但是服务器还是按照顺序,先回应A请求,完成后再回应B请求。

    http发展的各个阶段

    http 0.9 最早时候只支持传输html,请求中没有任何请求头。

    http 1.0 引入了请求头和响应头,这样的话就可以根据请求头区分传输的内容是图片还是html又或是js。

    http 1.1 针对http1.0每一次请求都会发送请求建立tcp链接,请求结束后断开tcp链接。这无疑是非常耗时的。所以在http 1.1中默认开启了一个请求头connect:keep-alive进行在一个tcp链接的复用。当然即使引入了长链接keep-alive,还存在一个问题就是基于http 1.0中是一个请求发送得到响应后才开始发送下一个请求,针对这个机制1.1提出了管线化pipelining机制,但是需要注意的是服务器对应同一tcp链接上的请求是一个一个去处理的,所以这就会导致一个比较严重的问题队头阻塞

    如果说第一个发送的请求丢包了,那么服务器会等待这个请求重新发送过来在进行返回处理。之后才会处理下一个请求。即使浏览器是基于pipelining去多个请求同时发送的。

    http 2.0 提出了很多个优化点,其中最著名的就是解决了http1.1中的队头阻塞问题。

    多路复用: 支持使用同一个tcp链接,基于二进制分帧层进行发送多个请求,支持同时发送多个请求,同时服务器也可以处理不同顺序的请求而不必按照请每个请求的顺序进行处理返回。这就解决了http 1.1中的队头阻塞问题。

    头部压缩: 在http2协议中对于请求头进行了压缩达到提交传输性能。

    Server push: http2中支持通过服务端主动推送给客户端对应的资源从而让浏览器提前下载缓存对应资源。

    http3.0: 基于tcp下就难免存在阻塞问题,如果发生丢包就需要等待上一个包。在http3彻底解决了tcp的队头阻塞问题,它是基于udp协议并且在上层增加了一层QUIC协议。

    关于http 3.0和2.0这部分我研究的不是很多,所以就不做详细的对比了。大家如果有更详细的建议可以在评论区留言。后续如果有必要我会补充这部分内容。

    关于http 1.1的pipelining机制和http 2.0的多路复用

    其实这个问题最开始我也是一直困惑的,他们究竟存在什么区别。直到有一天我看到了stackoverflow上这个答案

    HTTP/1.1 without pipelining: 必须响应 TCP 连接上的每个 HTTP 请求,然后才能发出下一个请求。

    HTTP/1.1 with pipelining: 可以立即发出 TCP 连接上的每个 HTTP 请求,而无需等待前一个请求的响应返回。响应将以相同的顺序返回。

    HTTP/2 multiplexing: TCP 连接上的每个 HTTP 请求都可以立即发出,而无需等待先前的响应返回。响应可以按任何顺序返回。

    浏览器渲染

    首先我们先来看一看关于浏览器加载的粗略加载图

    参考 https://zhuanlan.zhihu.com/p/575565899

    粗略来说浏览器的渲染过程带盖就是这样,但其实这其中涉及太多细节方面的知识点。比如一些文件的加载顺序,是否阻塞,CRP关键渲染路径等等...

    让我们一层一层来揭开浏览器渲染的面纱。

    css与js对于dom的影响

    css是否会阻塞Dom

    我们先来看看css对于dom的影响:

    1.对于css的加载是不阻塞dom的构建的。

    2.对于css的加载时会阻塞之后的dom节点的渲染的。

    js是否会阻塞Dom

    其实毋庸置疑,js的执行过程一定是会阻塞Dom Tree和Css OM的。

    其实这里大家只要把握一个原则,在渲染进程中JS线程和渲染线程是互斥的关系。

    为什么css放上边而js放在下面

    上边我们讲到了css的加载和解析并不会阻塞Dom的构建,但是会阻塞页面上之后元素的渲染。这也就造成了如果css放在顶部的话,后续Dom元素的渲染需要依赖本次css代码执行解析完成之后才会

    将css放在底部的话页面的确是会产生两次渲染的。但是第一次没有任何样式的渲染其实是一次“无效渲染”。

    我们利用chrome浏览器performance去分析将css放在底部的代码中发现实际上浏览器进行了两次元素的绘制,也就是说如果将css代码放在底部是会发生重绘(以及可能会引发回流),这个操作是非常耗时的一个过程。

    所以将css放在顶部的话:

    页面首次渲染浏览器仅仅会进行一次渲染,而不会造成多余的重绘和回流步骤。

    为什么js需要放在底部

    上边我们说到了关于js实际上是会阻塞Dom Tree的构建和渲染的。同时js依赖于前边的css文件加载完成后才会进行执行。

    将js放在了元素之前,首先在js执行完成之前是不会进行后续元素的构建和渲染的。只有等待js加载并且解析完成之后渲染线程才会继续之后的Dom Tree的构建以及页面的渲染

    js是会阻塞html解析和渲染的,同时需要注意js的执行是需要等待之前的css加载并且执行完毕。保证js可以操作样式的。所以css之后如果存在js那么css的加载过程也是可以间接性的阻塞DCL事件的。当然对于defer和async这两个属性我们会在后续深入讲解。这里额外有一点:在页面解析Html之前浏览器会额外扫描外部链接,将外部链接交给网络进程进行下载。所以css和js的下载可以是并行的。

    所以,我们之所以将js放在底部。是因为js放在底部是会等待页面渲染完毕后再去阻塞的执行后续js。

    js加载和解析是会阻塞后续dom的解析。

    css加载会阻塞页面的渲染。
    css加载不会阻塞后续dom解析(https://blog.csdn.net/qq_41462647/article/details/130161709)
    css加载会阻塞后续js的执行

    参考:https://zhuanlan.zhihu.com/p/458664335

    相关文章

      网友评论

          本文标题:全方位解析浏览器渲染原理

          本文链接:https://www.haomeiwen.com/subject/swabpdtx.html