这是一道十分经典的面试题,涉及到计网、 *** 作系统、web 的一系列知识,不管是深度还是广度,都能够很好地考察面试者的水平,相信很多程序员多多少少都接触到这道题,网上也有很多非常好的文章对此作出了解释,但能深入并且扩展开来的,少之又少。今天笔者会以面试者的角度来回答这个问题,逐步扩展开来,详细讲解这道题,既作为自己面试的准备,也帮助大家能更加深入的理解到其中的奥秘(原来我们再日常不过的 *** 作,还隐藏了这么多技术,而这也是前端的神奇所在啦),预计在真实面试中可以讲 15
分钟左右。
关于这道经典的面试题,笔者准备写三篇文章来对知识点进行深度和广度的挖掘:
- 深入探究“在浏览器输入URL到渲染页面”(上)过程剖析
- 深入探究“在浏览器输入URL到渲染页面”(中)性能优化
- 深入探究“在浏览器输入URL到渲染页面”(下)模拟面试
我们知道会有这么一个过程:
- 输入 URL 地址:在浏览器地址栏输入网站连接
- DNS 解析:将域名解析成 IP 地址
- TCP 连接:浏览器与服务器进行三次握手
- 发送 HTTP 请求
- 服务器处理请求并返回 HTTP 报文
- 浏览器解析渲染页面
- 断开连接:TCP 四次挥手
这个过程不能说错,但笔者觉得不够全面,因为在这背后还有更多的技术在默默支撑着这些功能的实现,我们一起来探究下。
先简述总体过程:
一、用户输入
- 用户在浏览器地址栏输入 url 并回车
二、URL 请求过程
- 浏览器处理用户输入,把处理后的 url 发送至网络进程
- 网络进程收到 url 请求后查看本地是否缓存了该资源,如果有则将该资源返回给浏览器进程
- 如果没有,网络进程向服务器发起 HTTP 请求(网络请求)以请求资源:
- DNS 解析:将域名解析成 IP 地址
- 如果请求协议是HTTPS,那么还需要建立TLS连接
- 利用 ip 地址和服务器建立 TCP 连接:浏览器与服务器进行三次握手
- 浏览器端构建请求头信息,并将其发送给服务器
- 服务器处理请求并返回 HTTP 报文给网络进程
- 处理状态码
三、断开连接
- TCP 四次挥手
四、浏览器进程开始准备渲染进程
- 浏览器进程检查当前url是否和之前打开的渲染进程根域名是否相同,如果相同,则复用原来的进程,如果不同,则开启新的渲染进程
五、提交文档
- 渲染进程准备好之后,需要先向渲染进程提交页面数据,我们称之为提交文档阶段
六、渲染阶段
- 渲染进程接收完文档信息之后,便开始解析页面和加载子资源,完成页面的渲染。
一、用户输入下面来详述这些过程的具体细节:
当用户在地址栏中输入信息时,地址栏会判断该信息是文字还是路径。
- 如果是文字,地址栏会使用浏览器默认的搜索引擎,来合成新的带有该文字的 url。
- 如果是路径(通常用户会输入如
baidu.com
的路径),那么地址栏若判断其符合 URL 规则,就把这段路径加上协议,合成完整的 url,就变成了https://www.baidu.com
。
当用户完成输入后并回车之后,浏览器便进入下图的状态:
从图中可以看出,标签页上的图标进入了加载状态,但此时页面仍是之前的页面,并没有立即替换成百度的首页。这是因为需要等待提交文档阶段,页面内容才会被替换。
而提交文档需要获得服务器返回所请求的文件信息,才能进行下一步 *** 作。
二、URL 请求过程用户已经完成了他的输入 *** 作,接下来便进入了页面资源请求过程。这时,浏览器进程通过进程间通信(IPC
)把 url 请求发送给网络进程,网络进程接收到 URL 请求后会查看本地缓存。
简单说一下:
浏览器进程主要负责用户交互、子进程管理和文件储存等功能。
网络进程是面向渲染进程和浏览器进程等提供网络下载功能。
渲染进程的主要职责是把从网络下载的HTML、JavaScript、CSS、图片等资源解析为可以显示和交互的页面。
这个我们在前面简述总流程时已经说过了,但是所谓的“查看本地缓存”是怎么个查看法,缓存又是存到何处,我们来探究下其中细节:
浏览器缓存所谓“查看本地缓存”,其实就是我们常听到的浏览器缓存。
浏览器缓存有两种,分别是 强缓存 和 协商缓存 。
浏览器在发送请求时,根据请求头的 Expires
和 Cache-Control
判断是否命中强缓存策略,如果命中,直接从缓存获取资源,不会发送请求了;如果没有命中,发送请求,根据请求头 Last-Modified
和 Etag
判断是否命中协商缓存,如果命中,直接从缓存获取资源,如果没有命中,则直接从服务端获取资源。
强制缓存就是向浏览器缓存查找该请求结果,并根据该结果的缓存规则来决定是否使用该缓存结果的过程。
怎么判断是否命中强缓存呢?有三种情况:
- ①第一次请求,不存在缓存结果和缓存标识,这时强缓存失效,直接向服务器发送请求。
- ②存在缓存结果和缓存标识,但已经失效,这时强缓存失败,使用协商缓存。
- ③存在缓存结果和缓存标识,且尚未失效,这时强缓存生效,直接返回该结果。
怎么做到强缓存呢?可以通过设置两种 HTTP Header 实现:Expires
和 Cache-Control
(Cache-Control
的优先级高于 Expires
)。
Expires
是 HTTP/1.0 控制网页缓存的字段,其值为服务器返回该请求结果缓存的到期时间。也就是说,Expires = max-age + 请求时间
,需要和Last-modified
结合使用。
Expires
控制缓存的原理是使用客户端的时间与服务端返回的时间作对比,那么如果①客户端的时间被人为修改,或者②由于时区不同服务端的时间不准确,那么强缓存会直接失效。因此到了 HTTP/1.1Expire
已经被Cache-Control
替代,这也是为什么Expires
和Cache-Control
,优先级却低于Cache-Control
的原因。
Cache-Control
是 HTTP/1.1 控制网页缓存的字段,主要取值为:public
:默认值,所有内容都将被缓存(浏览器和代理服务器都可以缓存,并且在多用户间共享)private
:所有内容只有客户端可以缓存(不能在用户间共享)no-cache
:客户端缓存内容,但是否使用缓存则需要经过协商缓存来验证决定no-store
:所有内容都不会被缓存,既不强缓存,也不协商缓存max-age=xxx
:单位为s
,缓存内容将在 xxx 秒后失效s-maxage
:单位为s
,同max-age
,只用于共享缓存(比如CDN缓存)。- 比如,当
s-maxage=60
时,在这60
秒中,即使更新了 CDN 的内容,浏览器也不会进行请求。也就是说max-age
用于普通缓存,而s-maxage
用于代理缓存。如果存在s-maxage
,则会覆盖掉max-age
和Expires
header。
- 比如,当
must-revalidate
:指定如果页面是过期的,则去服务器进行获取。
注意:规则可以同时多个
强缓存不会向服务器发送请求,直接从缓存中读取资源,在 chrome 控制台的 Network 选项中可以看到该请求返回 200
的状态码,并且 Size
显示 from disk cache
(磁盘缓存) 或 from memory cache
(内存缓存) 。
先加载一次页面,然后刷新一下就可以在控制台看到哪里缓存了:
那问题来了:什么时候会使用 from disk cache
,什么时候会使用 from memory cache
呢?
答案是先 memory
再 disk
,原因如下:
from memory cache
有两个特点,分别是快速读取和时效性。- 快速读取:内存缓存会将编译解析后的文件,直接存入该进程的内存中,占据该进程一定的内存资源,以方便下次运行使用时的快速读取。
- 时效性:一旦该进程关闭,则该进程的内存则会清空。
from disk cache
直接将缓存写入硬盘文件中,读取缓存需要对该缓存存放的硬盘文件进行I/O *** 作,然后重新解析该缓存内容,读取复杂,速度比内存缓存慢。- 在浏览器中,浏览器会在 js 和图片等文件解析执行后直接存入内存缓存中,那么当刷新页面时只需直接从内存缓存中读取
(from memory cache)
;而 css 文件则会存入硬盘文件中,所以每次渲染页面都需要从硬盘读取缓存(from disk cache)
。
协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程。
怎么判断是否命中协商缓存呢?有两种情况:
- ①协商缓存生效,返回
304
,表示该资源无更新。 - ②协商缓存失效,返回
200
和请求结果,表示该资源更新了。
怎么做到协商缓存呢?也可以通过设置两种 HTTP Header 实现:Last-Modified/If-Modified-Since
和 Etag/If-None-Match
(Etag/If-none-match
的优先级高于 Last-Modified/If-Modified-Since
)。
Last-Modified/If-Modified-Since
Last-Modified
是服务器响应请求时,返回该资源文件在服务器最后被修改的时间。If-Modified-Since
是客户端再次发起该请求时,携带上次请求返回的Last-Modified
值。
服务器收到请求,发现请求头含有If-Modified-Since
,会根据该字段值与该资源在服务器的最后被修改时间做比较:若服务器的资源最后被修改时间大于If-Modified-Since
的字段值,则重新返回资源,状态码为200
;否则返回304
,代表资源无更新。
Etag/If-None-Match
Etag
是服务器响应请求时,返回当前资源文件的一个唯一标识(由服务器生成一段 hash 字符串)If-None-Match
是客户端再次发起该请求时,携带上次请求返回的唯一标识Etag
值,通过此字段告诉服务器该资源上次请求返回的唯一标识值。
服务器收到请求,发现该请求头中含有If-None-Match
,会根据该字段值与该资源在服务器的Etag
值作对比:若发现一致则返回303
,代表资源无更新,继续使用缓存文件;不一致则重新返回资源文件,状态码为200
。
强制缓存优先于协商缓存进行,若强制缓存(Expires
和 Cache-Control
)生效则直接使用缓存,若不生效则进行协商缓存(Last-Modified / If-Modified-Since
和 Etag / If-None-Match
),协商缓存由服务器决定是否使用缓存,若协商缓存失效,那么代表该请求的缓存失效,重新获取请求结果,再存入浏览器缓存中;生效则返回 304
,继续使用缓存。
图解:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fG7i2XEH-1651986955364)(https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b8013b5d62c948cea82bef06418f4548~tplv-k3u1fbpfcp-watermark.image?)]
如果你想亲手 *** 作浏览器缓存,体会其中奥妙,这篇文章 可以助你一臂之力 _
到这里,“查看本地缓存”就结束了,如果仍然需要向服务器请求资源的话,就需要先进行下面的 *** 作了。
DNS 解析要向域名所在服务器请求资源,首先当然得知道这个域名的 ip 地址是什么,才可以发起请求。关于这个 ip 地址,计算机会先在本地进行查找,这里会分成三小步:
- 先到浏览器的 DNS 缓存中查询是否有对应记录,如有则直接返回 ip ,完成解析,如果没有则下一步;
- 判断软件或浏览器是否有进行域名直接查询,如果有的话,会直接连到软件服务商提供的 DNS 服务器上,并返回 ip ;如果没有,则继续查询 *** 作系统的缓存,如有缓存则直接返回 ip ,如果没有则下一步;
- 查看本地
host
文件(因为hosts
文件会建立域名到 ip 地址的绑定关系),比如windows
的host
文件一般位于“C:\Windows\System32\drivers\etc”
,如果这里也没有的话就需要到本地 DNS 服务器上查找了。
如果至此还没有命中域名,这时浏览器才会向本地 DNS 服务器发起一个请求来解析这个域名(题外话:通常本地域名服务器在你的城市的某个角落,距离不会很远,一般都会缓存域名解析结果,大约 80% 的域名解析到这里就完成了)。
提一嘴:DNS 域名服务器一般分3种,分别是根域名服务器(.)、顶级域名服务器(.com、.cn、.net 等)、权限域名服务器(xxx.com、xxx.cn、xxx.net 等)。
- 查询 DNS 请求到达本地 DNS 服务器之后,本地 DNS 服务器会首先查询它自己的缓存记录,如果缓存中有此条记录,就可以直接返回结果。如果没有,本地 DNS 服务器还要向根域名服务器进行查询【由本地 DNS 服务器代替浏览器发起这个请求】。
- 根域名服务器没有记录具体的域名和 ip 地址的对应关系,它会返回域名的顶级域名服务器的地址,让本地 DNS 服务器去这个地址获取 ip 。
- 于是本地 DNS 服务器得到新的地址后,向顶级域名服务器发出请求。顶级域名服务器收到请求,会返回权威域名服务器的地址,让本地 DNS 服务器其这个地址获取 ip 。
- 于是本地 DNS 服务器得到新的地址后,向权限域名服务器发出请求。权限域名服务器收到请求,在自己的缓存表中发现了该域名和 ip 地址的对应关系,就将 ip 地址返回给本地 DNS 服务器。
- 本地 DNS 服务器将获取到与域名对应的 ip 地址返回给客户端,并且将域名和 ip 地址的对应关系保存在缓存中,以备下次别的用户查询时使用。
- 这样,DNS 解析就完成了。
这样解释可能比较抽象,也不太容易懂,我们来看个栗子:
用户输入的 url 为 https://www.baidu.com/
,这里假设本地缓存无效,浏览器直接向本地 DNS 服务器发起域名解析请求。
- 浏览器发送带有 url 的请求,询问本地 DNS 服务器:我这个域名的 ip 是什么?
- 本地 DNS 服务器:咱也不知道啊,我帮你问问吧,待会把结果告诉你。
- 本地 DNS 服务器发送带有 url 的请求,询问根服务器:这个域名的 ip 你知道吗?
- 根域名服务器:我给你
.com
顶级域名服务器的地址,你去问问它知不知道吧。 - 本地 DNS 服务器拿到
.com
顶级域名服务器的地址,便发起请求,询问.com
顶级域名服务器:这个域名的 ip 你知道吗? .com
顶级域名服务器:我给你baidu.com
权限域名服务器的地址,你去问问它知不知道吧。- 本地 DNS 服务器拿到
baidu.com
权限域名服务器的地址,便发起请求,询问baidu.com
权限域名服务器:这个域名的 ip 你知道吗? baidu.com
权限域名服务器:我找找看哈,是1.2.3.4
,诺,给你。- 本地 DNS 服务器拿到 ip 后,将其给了浏览器:我问到了 ip 了,这就是了,你拿去吧。
- 浏览器:好的,我把它存起来一段时间,这段时间就可以不用麻烦你了。
这样比较好理解一些有木有,还是不行的话可以看下图解:
关于 DNS 解析的 TTL 参数:
购买域名后,我们在配置 DNS 解析的时候,有一个参数常常容易忽略,就是 DNS 解析的 TTL 参数(Time To Live)。TTL 这个参数告诉本地 DNS 服务器,域名缓存的最长时间。
用阿里云解析来举例,阿里云解析默认的 TTL 是 10 分钟,10 分钟的含义是,本地 DNS 服务器对于域名的缓存时间是 10 分钟,10 分钟之后,本地 DNS 服务器就会删除这条记录,删除之后,如果有用户访问这个域名,就要重复一遍上述复杂的流程。
其实,如果网站已经进入稳定发展的状态,不会轻易更换 IP 地址,我们完全可以将 TTL 设置到协议最大值,即24小时。带来的好处是,让域名解析记录能够更长时间的存放在本地DNS服务器中,以加快所有用户的访问。
TCP 三次握手推荐阅读:超详细 DNS 协议解析
DNS 解析完成之后,浏览器已经知道了要请求域名的 ip 地址,可以建立向该地址建立连接了:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7vY6meZP-1651986955366)(https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b1e115e67abf4225b5e3c18920c8fe14~tplv-k3u1fbpfcp-watermark.image?)]
- 一开始客户端和服务器都处于
CLOSED
状态。然后服务器开始监听某个端口,并进入LISTEN
状态。
CLOSED
:没有任何连接状态
LISTEN
:侦听来自远方 TCP 端口的连接请求
- 客户端主动发起连接,开始第一次握手:客户端向服务器发送
SYN
报文(SYN = 1
,表明这是一个 TCP 连接请求报文段),并指明客户端的初始化序列号seq = x
(表示本报文段所发送的数据的第一个字节的序号)。客户端进入SYN-SENT
状态,等待服务器的确认。
SYN-SENT
:同步已发送状态,在发送连接请求后等待匹配的连接请求
- 服务器收到客户端的
SYN
报文,如果同意建立连接,就开始第二次握手:服务器发送SYN + ACK
报文作为应答(SYN = 1, ACK = 1
,表明这是一个 TCP 连接请求确认报文段),并指定自己的初始化序列号seq = y
(作为 TCP 服务器进程所选择的初始序号),同时把客户端的seq + 1
作为确认号ack
的值,即ack = x + 1
(表示已经收到了客户端发来的SYN
报文,希望收到的下一个数据的第一个字节的序号是x + 1
)。服务器进入SYN-RCVD
状态。
SYN-RCVD
:在收到和发送一个连接请求后等待对连接请求的确认
- 客户端收到服务器响应的
SYN
报文之后,开始第三次握手:客户端发送ACK
报文(ACK = 1
,表明这是一个普通的 TCP 确认报文段),把服务器的seq + 1
作为ack
的值,即ack = y + 1
(表示已经收到了服务器发来的SYN
报文,希望收到的下一个数据的第一个字节的序号是y + 1
),并指明此时客户端的序列号seq = x + 1
(这是因为 TCP 客户进程发送的第一个 TCP 报文段的序号为x
,并且不携带数据)。客户端进入ESTABLISHED
状态。
ESTABLISHED
:代表一个打开的连接,数据可以传送给用户
- TCP 服务器进程收到该确认报文段后也进入
ESTABLISHED
状态。现在,TCP 双方都进入了连接已建立状态,它们可以基于已建立好的 TCP 连接进行通信,即开始发送 HTTP 请求。
HTTP 报文分为请求报文和响应报文,都有固定的格式以及各自的属性。
详情可见:HTTP首部
-
在收到服务器返回的响应报文后,网络进程开始解析响应头。
如果发现返回的状态码是301
或302
,那么说明服务器需要浏览器重定向到其他 url ,于是网络进程会从响应头的Location
字段里读取重定向的地址,然后再发起新的 HTTP 或者 HTTPS 请求,这也意味着要从头开始重复以便之前的流程。 -
在处理了跳转信息之后,接下来就要看看服务器返回来的数据类型了。
怎么查看这个数据类型呢?答案是响应头的Content-Type
字段。
Content-Type
表示服务器返回的响应体数据的类型,浏览器会根据它的值来决定如何显示响应体的内容。
如果值为 text/html
,这就是告诉浏览器,服务器返回的数据是 HTML 格式;如果值为 application/octet-stream
,就代表显示数据是字节流类型的,通常情况下,浏览器会按照下载类型来处理该请求。
三、TCP 四次挥手注意:如果服务器配置
Content-Type
不正确,比如将text/html
类型配置成application/octet-stream
类型,那么浏览器可能会曲解文件内容,可能会将一个本来是用来展示的页面,变成一个下载文件。
浏览器收到服务器响应的数据后,要看看请求头或响应头中是否包含 Connection: Keep-Alive
,表示建立了持久连接,这样 TCP
连接会一直保持,之后请求统一站点的资源会复用这个连接;否则进行 TCP 四次挥手断开连接:
- 一开始客户端和服务器都处于
ESTABLISHED
状态。当数据传输结束后,通信的双方均可释放连接。 - 假设是客户端先发起关闭请求,开始第一次挥手:客户端发送
FIN
报文(FIN = 1
,表明这是一个 TCP 连接释放报文段,同时也对之前收到的报文段进行确认),报文中指定一个序列号seq = u
(表示 TCP 客户进程之前已传送过的数据的最后一个字节的序号+1
)。客户端进入FIN-WAIT-1
状态。
FIN-WAIT-1
:待远程TCP的连接中断请求,或先前的连接中断请求的确认
- 服务器收到
FIN
包,结束ESTABLISHED
阶段,开始第二次挥手:服务器发送ACK
报文(ACK = 1
,表明这是一个普通的 TCP 确认报文段),并且令ack = u + 1
(表明这是在收到客户端报文的基础上,将其序号值加1
作为本段报文确认号ack
的值,这是对 TCP 连接释放报文段的确认),设置seq = v
。服务器进入CLOSE_WAIT
状态。
CLOSE-WAIT
:等待从本地用户发来的连接中断请求;
- 此时的 TCP 处于半关闭状态,从 “客户端–>服务器” 方向的连接释放,客户端无法再发送数据给服务器。客户端收到服务器的确认后,进入
FIN-WAIT-2
状态,等待服务器发出的连接释放报文段。
FIN-WAIT-2
:从远程TCP等待连接中断请求
- 如果服务器没有要东西要传送给客户端,要断开连接了,就开始第三次挥手:发送
FIN + ACK
报文(FIN = 1, ACK = 1
,表示服务器已经准备好释放连接了),并指定确认号ack = u + 1
(表示这是在收到客户端报文的基础上,将其序号seq
的值加1
作为本段报文确认号 ack 的值),同时指定一个序列号seq = w
。服务器进入LAST-ACK
状态,等待客户端的确认。
LAST-ACK
- 等待原来发向远程TCP的连接中断请求的确认;
- 客户端收到服务器发来的
FIN + ACK
包之后,确认服务器已经做好释放连接的准备了,于是开始第四次挥手:发送一个ACK
报文(ACK = 1
,表示接收到了服务器准备好释放连接的信号)作为应答,令ack = w + 1
(表示这是在收到了服务器报文的基础上,将其序号 seq 的值+1
作为本段报文确认号的值),并且seq = u + 1
(表示这是在已收到服务器报文的基础上,将其确认号 ack 的值作为本段序号的值)。客户端进入TIME-WAIT
状态。
TIME-WAIT
:等待足够的时间以确保远程TCP接收到连接中断请求的确认
- 随后客户端开始在
TIME-WAIT
阶段等待2 MSL
。 - 服务器收到从客户端发出的 TCP 报文之后结束
LAST-ACK
阶段,进入CLOSED
阶段。由此正式确认关闭从 “服务器–>客户端” 方向的连接。 - 客户端在等待了
2 MSL
之后,自动结束TIME-WAIT
阶段,进入CLOSED
阶段。至此,四次握手完成。
默认情况下,Chrome 会为每一个页面分配一个渲染进程,也就是说,在浏览器上每打开一个新的 Tab 页面就会创建一个新的渲染进程(这句话其实不太正确,我们后面会讲到)。
如下图所示,我在百度首页打开了百度贴吧,总共开了两个标签页,查看 Chrome 的任务管理器,我们发现确实创建了两个渲染进程,两个进程 ID 都不一样。
但这并不是说打开一个标签页就一定会创建新的渲染进程,只要两个标签页属于同一个站点,浏览器就会让这两个标签页直接运行在同一个渲染进程中。
Q:那么什么叫“同一站点”呢?
A:我们将“同一站点”定义为:只要协议 + 根域名相同,就说他们属于同一站点。
下面这三个 url 就属于同一站点:
https://www.baidu.com
https://www.baidu.com:8080
https://map.baidu.com/
因为他们的协议都是 https://
,根域名都是 baidu.com
,因此均属于同一站点。这也意味着浏览器不会创建三个渲染进程,而是只创建一个渲染进程,其他标签页复用该进程。
- 渲染进程准备好后,还不能立即进入文档解析状态,因为此时的文档数据还在网络进程中,并没有提交给渲染进程,因此浏览器进程向渲染进程发起“提交文档”的消息,渲染进程接收到消息后与网络进程建立传输数据的“管道”。
- 于是网络进程就开始传送数据。当渲染进程接收完数据后,会返回“确认提交”的消息给浏览器进程。
- 浏览器进程收到确认消息后更新浏览器界面。
六、浏览器页面的渲染过程这就是为什么在浏览器的地址栏输入了一个地址之后,之前的页面没有立马消失,而是要加载一会才会更新页面。这中间要请求资源、等待响应、提交数据等等,当然不会一下子就完成好。
注意:此时页面还没渲染完毕,只是将浏览器的界面状态更新了而已,如前进后退的状态、地址栏信息、安全状态、页面变化(可能是页面变成白屏或者骨架屏吧?)。
当文档数据被提交后,渲染进程就要开始页面解析和子资源加载了。
- 解析
HTML
构建DOM
树 - 解析
CSS
构建CSSOM
树 - 创建
Layout
(布局) 树,并计算元素的布局信息。- 遍历
DOM
树中的所有可见节点,并把这些节点加到布局中; - 而不可见的节点会被布局树忽略掉,如
head
标签下面的全部内容,再比如属性为dispaly:none
等的元素也不会被包进布局树
- 遍历
- 对布局树进行分层,并生成分层树。
- 通常情况下,并不是布局树的每个节点都包含一个图层,如果一个节点没有对应的层,那么这个节点就从属于父节点的图层。
- 为每个图层生成绘制列表,并将其提交到合成线程。
- 渲染引擎会把一个图层的绘制拆分成很多小的绘制指令,然后再把这些指令按照顺序组成一个待绘制列表。当图层的绘制列表准备好之后,主线程会把该绘制列表提交(commit)给合成线程。
- 合成线程将图层分成图块,并在光栅化线程池中将图块转换成位图。
- 当所有图块都被光栅化后,合成线程就会将它们合并成一张图片,并生成一个绘制图块的命令
DrawQuad
,然后将该命令发送给浏览器进程。 - 浏览器进程里的一个叫
viz
的组件是专门用来接收合成线程发过来的DrawQuad
命令,然后根据该命令将其页面内容绘制到内存中,最后再将内存显示在屏幕上。(然后发送给显卡。) - 接着放入通过显卡缓存的后缓冲区,然后显卡中前后缓冲去交换,显示器显示页面并显示到显示器上。
- 当页面生成完成,渲染进程会发送一个消息给浏览器进程,浏览器收到消息后,会停止标签图标上的加载动画。
最后①你以为渲染过程只是这样吗?那么大错特错了,就解析
HTML
而言,里面还有很多学问。
②你知道为什么第3步不是应该生成Render
树吗?因为那已经是之前的事情了,现在 Chrome 团队已经做了大量的重构,已经没有生成Render Tree
的过程了。
③你知道需要满足什么条件,渲染引擎才会为特定的节点创建新的层吗?需要拥有层叠上下文属性的元素会被提升为单独的一层;或者需要剪裁(clip)的地方也会被创建为图层。
④你知道什么是光栅化吗?所谓栅格化,是指将图块转换为位图。还有很多更加深层次的东西这里没有写出来,笔者强推:
poetry 的 渲染流程(上):HTML、CSS和JavaScript是如何变成页面的
神三元的 002 说一说从输入URL到页面呈现发生了什么?——解析算法篇
这两篇文章写得非常之好,分享给大家。前端的世界真的非常神奇,这两篇文章会告诉你,你平常看到的那些很好看的网页背后,究竟做了些什么。这里笔者就不班门弄斧了。
看完这篇文章后,你的大脑应该会感觉很充实,学到了很多底层知识,同时知道了在面试中遇到这道题时该怎么回答,这是好事。但其实这里面还可以继续深挖下去,你知道的,面试官的水平通常会比面试者高,他们可能会问更加深层次的东西,要想回答上来,我们平时要注意多思考,多问为什么。
这里笔者也针对这个经典面试题写了一篇文章:深入探究“在浏览器输入URL到渲染页面”(下)模拟面试,如果担心在面试时被问住,可以先收藏下,不过这篇文章笔者还没写好,后续会补上。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)