说一说从 URL 输入到页面呈现到底发生了什么?
前言
这是面试过程中一道高频考题。
从面试官的角度思考:
- 出现频繁,可能是因为面试官通常喜欢问一些考察可深可浅的题目
- 很多面试管喜欢根据我们应聘者的考题回答中,甚至我们随口说到的知识点,继续追问
基本回答:
- 浏览器解析 URL 获取协议,主机,端口, path
- 浏览器获取主机 ip 地址
- 建立 TCP 连接,然后发送 HTTP 请求
- 服务器将响应报文通过 TCP 连接发送回浏览器,浏览器接收 HTTP 响应,根据资源类型决定如何处理(假设资源为 HTML 文档)
- 解析 HTML 文档,构件 DOM 树,下载资源,构造 CSSOM 树,执行 js 脚本,最后展现出来给用户
如果应聘者只回答了上述步骤,很多关键步骤(前端应该了解的知识点)没有提及,很有可能达不到面试官想要的回答效果。
笔者针对一些关键步骤,具体展开说明。让这道题成为我们面试考卷中的加分项。
网络请求
构建请求
浏览器会构建请求行:
// 请求方法是 GET,路径为根路径,HTTP 协议版本为 1.1
GET / HTTP/1.1
然后根据 Cache-control 和 Expires 字段,检查强缓存,如果命中直接使用,否则进入下一步。关于强缓存,如果不清楚可以参考下图:
DNS 解析
由于我们输入的是域名,而数据包是通过IP地址传给对方的。因此我们需要得到域名对应的IP地址。这个过程需要依赖一个服务系统,这个系统将域名和 IP 一一映射,我们将这个系统就叫做DNS(域名系统)。
DNS 协议提供通过域名查找 IP 地址,或逆向从 IP 地址反查域名的服务。得到具体 IP 的过程就是DNS解析。
DNS 是一个网络服务器,我们的域名解析简单来说就是在 DNS 上记录一条信息记录。
例如 baidu.com 220.114.23.56(服务器外网IP地址)80(服务器端口号)
浏览器通过域名去查询 URL 对应的 IP :
- 浏览器缓存:浏览器会按照一定的频率缓存 DNS 记录
- 操作系统缓存:如果浏览器缓存中找不到需要的 DNS 记录,那就去操作系统中找
- 路由缓存:路由器也有 DNS 缓存
- ISP 的 DNS 服务器:ISP 是互联网服务提供商( Internet Service Provider )的简称,ISP 有专门的 DNS 服务器应对 DNS 查询请求
- 根服务器:ISP 的 DNS 服务器还找不到的话,它就会向根服务器发出请求,进行递归查询(DNS 服务器先问根域名服务器 .com 域名服务器的 IP 地址,然后再问 .baidu 域名服务器,依次类推)
建立 TCP 连接
TCP 三次握手的过程如下:
- 客户端发送一个带 SYN=1,Seq=X 的数据包到服务器端口(第一次握手,由浏览器发起,告诉服务器我要发送请求了)
- 服务器发回一个带 SYN=1, ACK=X+1, Seq=Y 的响应包以示传达确认信息(第二次握手,由服务器发起,告诉浏览器我准备接受了,你赶紧发送吧)
- 客户端再回传一个带 ACK=Y+1, Seq=Z 的数据包,代表“握手结束”(第三次握手,由浏览器发送,告诉服务器,我马上就发了,准备接受吧)
谢希仁著《计算机网络》中讲“三次握手”的目的是“为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误”。
发送 HTTP 请求
现在 TCP 连接建立完毕,浏览器可以和服务器开始通信,即开始发送 HTTP 请求。浏览器发 HTTP 请求要携带三样东西:请求行、请求头和请求体。
[图片上传失败...(image-f9c53-1587981019451)]
1.请求行包含请求方法、URL、协议版本
- 请求方法包含 8 种:GET、POST、PUT、DELETE、PATCH、HEAD、OPTIONS、TRACE
- URL 即请求地址,由 <协议>://<主机>:<端口>/<路径>?<参数> 组成
- 协议版本即 http 版本号
POST /user.html HTTP/1.1
2.请求头包含请求的附加信息,由关键字/值对组成,如下
// 服务器可以接受的文件格式
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng;q=0.8,application/signed-exchange;v=b3
// 指定浏览器可以支持的 Web 服务器返回的内容压缩编码类型
Accept-Encoding: gzip, deflate, br
// 浏览器支持的语言
Accept-Language: zh-CN,zh;q=0.9
// 缓存机制
Cache-Control: no-cache
// 是否需要持久连接
Connection: keep-alive
// 发送该请求域名下所有 Cookie 值到服务器
Cookie: /* 省略cookie信息 */
// 指定请求的服务器的域名和端口号
Host: www.baidu.com
Pragma: no-cache
Upgrade-Insecure-Requests: 1
// 用户代理 UA,包含发出请求的用户信息
User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1
3.请求体,可以承载多个请求参数的数据,包含回车符、换行符和请求数据,一般在 POST 方法下存在。
网络响应
跟请求部分类似,网络响应具有三个部分:响应行、响应头和响应体。
1.响应行包含:协议版本,状态码,状态码描述
HTTP/1.1 200 OK
状态码规则如下:
- 1xx:指示信息--表示请求已接收,继续处理
- 2xx:成功--表示请求已被成功接收、理解、接受
- 3xx:重定向--要完成请求必须进行更进一步的操作
- 4xx:客户端错误--请求有语法错误或请求无法实现
- 5xx:服务器端错误--服务器未能实现合法的请求
2.响应头部包含响应报文的附加信息,由 名/值 对组成,如下:
// 缓存机制
Cache-Control: no-cache
Connection: keep-alive
Content-Encoding: gzip
// 表示具体请求中的媒体类型信息,决定浏览器将以什么形式、什么编码读取这个文件
Content-Type: text/html;charset=utf-8
// 原始服务器消息发出的时间
Date: Wed, 04 Dec 2019 12:29:13 GMT
// Web 服务器软件名称
Server: apache
// 由服务器端向客户端发送 cookie
Set-Cookie: rsv_i=f9a0SIItKqzv7kqgAAgphbGyRts3RwTg%2FLyU3Y5Eh5LwyfOOrAsvdezbay0QqkDqFZ0DfQXby4wXKT8Au8O7ZT9UuMsBq2k; path=/; domain=.baidu.com
这里注意下 Set-Cookie 中关于网络安全方面的两个值:HttpOnly、SameSite
设置了 HttpOnly 属性的 cookie 不能使用 JavaScript 经由 Document.cookie 属性、XMLHttpRequest 和 Request APIs 进行访问,以防范跨站脚本攻击(XSS)。
SameSite=Lax 允许服务器设定一则 cookie 不随着跨域请求一起发送,这样可以在一定程度上防范跨站请求伪造攻击(CSRF)。
响应完成之后要判断 Connection 字段,如果请求头或响应头中包含 Connection: Keep-Alive ,表示建立了持久连接,这样 TCP 连接会一直保持,之后请求统一站点的资源会复用这个连接。
否则断开 TCP 连接, 请求-响应流程结束。
3.响应主体包含回车符、换行符和响应返回数据,并不是所有响应报文都有响应数据
总结浏览器端的网络请求过程:
image浏览器解析渲染页面
image浏览器解析渲染页面分为以下五个步骤:
- 根据 HTML 解析出 DOM 树
- 根据 CSS 解析生成 CSS 规则树
- 结合 DOM 树和 CSS 规则树,生成渲染树
- 根据渲染树计算每一个节点的信息
- 根据计算好的信息绘制页面
image回流时,以上流程会重新走一遍。重绘时,会重新计算样式,跳过中间步骤直接生成绘制列表。可见,重绘不一定导致回流,但回流一定发生了重绘。
构建 DOM 树
-
HTML语法定义
HTML的词汇与句法定义在w3c组织创建的规范中。当前版本是HTML4,HTML5的工作正在进行中。
-
不是上下文无关语法
在对解析器的介绍中看到,语法可以用类似 BNF 的格式规范地定义。不幸的是所有常规解析器的讨论都不适用于 HTML (我提及它们并不是为了娱乐,它们可以用于解析 CSS 和 JavaScript )。HTML 无法用解析器所需的上下文无关的语法来定义。过去 HTML 格式规范由 DTD (Document Type Definition) 来定义,但它不是一个上下文无关语法。
HTML 与 XML 相当接近。XML 有许多可用的解析器。HTML 还有一个 XML 变种叫 XHTML ,那么它们主要区别在哪里呢?区别在于 HTML 应用更加”宽容”,它容许你漏掉一些开始或结束标签等。它整个是一个“软”句法,不像 XML 那样严格死板。 总的来说这一看似细微的差别造成了两个不同的世界。一方面这使得 HTML 很流行,因为它包容你的错误,使网页作者的生活变得轻松。另一方面,它使编写语法格式变得困难。所以综合来说,HTML 解析并不简单,现成的上下文相关解析器搞不定,XML 解析器也不行。
-
解析算法
- 标记化
- 建树
对应的两个过程就是分词和语法分析(参考Babel 编译的解析过程)。
这里举例重点介绍下
HTML5
的容错机制:-
使用
</br>
而不是<br>
if (t->isCloseTag(brTag) && m_document->inCompatMode()) { reportError(MalformedBRError); t->beginTag = true; }
全部换为
<br>
的形式。 -
表格离散
<table> <table> <tr><td>inner table</td></tr> </table> <tr><td>outer table</td></tr> </table>
WebKit 会自动转换为:
<table> <tr><td>outer table</td></tr> </table> <table> <tr><td>inner table</td></tr> </table>
-
表单元素嵌套
这时候直接忽略里面的
form
。
样式计算
CSS 样式来源一般为三种:
- link 标签引用
- style 标签中样式
- 元素内嵌 style 属性
格式化样式表
浏览器无法直接识别 CSS 样式文本,这里渲染引擎接收到 CSS 文本之后将其转化为一个结构话的对象,即 styleSheets 。
可以在浏览器控制台输入 document.styleSheets
来查看这个最终结构(包含上述三种 CSS 来源)。
标准化样式属性
有一些渲染引擎不容易直接理解的 CSS 样式数值,需要在计算样式之前将它们标准化。如:em
-> px
,red
-> #ff0000
,bold
-> 700
等等。
计算每个节点的具体样式
计算具体样式主要遵循两个规则:继承和层叠
-
继承:
每个子节点都会默认继承父节点的样式属性,如果父节点中没有找到,就采用浏览器默认样式,也叫
UserAgent样式
。 -
层叠:
CSS 的层叠性体现在,最终的样式取决与各个属性共同作用的结果。
计算完样式之后,所有样式值会被挂载到 window.getComputedStyle
中,也就是可以通过 JS 获取计算后的样式。
生成布局树
布局树生成主要分两部:
- 遍历生成的 DOM 树节点,并把它们添加到布局树中
- 计算布局树节点的坐标位置
布局树只包含可见元素,对于 head 标签和设置了 display: none 的元素将不会被放入其中。
如果想了解布局的细节,可以读一读人人 FED 团队的文章从Chrome源码看浏览器如何layout布局。
构建图层树
这里分两种情况,一种是显式合成,一种是隐式合成。
显式合成
一、拥有层叠上下文的节点
层叠上下文也基本上是有一些特定的 CSS 属性创建的,一般有以下情况:
-
HTML 根元素本身就具有层叠上下文
-
普通元素设置 position 不为 static 并且设置了 z-index 属性,会产生层叠上下文
-
元素的 opacity 值不是 1
-
元素的 transform 值不是 none
-
元素的 filter 值不是 none
-
元素的 isolation 值是 isolate
-
will-change 指定的属性值为上面任意一个
二、需要剪裁的地方
比如一个 div,你只给他设置 100 * 100 像素的大小,而你在里面放了非常多的文字,那么超出的文字部分就需要被剪裁。当然如果出现了滚动条,那么滚动条会被单独提升为一个图层。
隐式合成
简单说就是层叠等级低的节点被提升为单独的图层之后,那么所有层叠等级比它高的节点都会成为一个单独的图层。
这个隐式合成其实隐藏着巨大风险,如果在一个大型应用中,当一个 z-index 比较低的元素被提升为单独图层之后,层叠在它上面的元素统统会被提升为单独的图层,可能会增加上千个图层,大大增加内存压力,甚至直接让页面崩溃。这就是层爆炸的原理
当需要 repaint 时,只需要 repaint 本身,而不会影响到其他层。
生成绘制列表
渲染引擎会将图层的绘制拆分成一个个绘制指令,比如先画背景、再描绘边框......然后将这些指令按顺序组合成一个待绘制列表。
大家可以 F12
打开 Chrome 开发者工具,在设置栏展开 more tools
,然后选择 Layers
面板,就能看到绘制列表了。
后面就是渲染进程的主线程把绘制列表提交给合成线程。然后合成线程选择视口附近的图块,把它交给栅格化线程池生成位图。
栅格化操作完成后,合成线程会生成一个绘制指令 DrawQuad
,并发送给浏览器进程。浏览器进程中的 viz 组件
接收到命令,把页面内容绘制到内存,也就是生成了页面。
断开连接
当数据传送完毕,需要断开 TCP 连接,此时发起四次挥手。
image- 发起方往被动方发送报文,Fin、Ack、Seq,表示已经没有数据传输了。并进入 FIN_WAIT_1 状态。(请求报文发送完成)
- 被动方发送报文,Ack、Seq,表示同意关闭请求。此时主机发起方进入 FIN_WAIT_2 状态。(请求报文接受完成)
- 被动方向发起方发送报文段,Fin、Ack、Seq,请求关闭连接。并进入 LAST_ACK 状态。(响应报文发送完成)
- 发起方向被动方发送报文段,Ack、Seq。然后进入等待 TIME_WAIT 状态。被动方收到发起方的报文段以后关闭连接。发起方等待一定时间未收到回复,则正常关闭。(响应报文接受完成)
参考文章
感谢
如果本文对你有帮助,就点个赞支持下吧!感谢阅读。