说一说从 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 的容错机制: 使用
而不是
if (t->isCloseTag(brTag) && m_document->inCompatMode()) { reportError(MalformedBRError); t->beginTag = true; } 全部换为
的形式。 表格离散
inner table
outer table WebKit 会自动转换为:
outer table
inner 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 状态。被动方收到发起方的报文段以后关闭连接。发起方等待一定时间未收到回复,则正常关闭。(响应报文接受完成) 参考文章 从URL输入到页面展现到底发生什么? (1.6w字)浏览器灵魂之问,请问你能接得住几个? 大揭秘!“恐怖”的阿里一面,我究竟想问什么 感谢 如果本文对你有帮助,就点个赞支持下吧!感谢阅读。

本文章由javascript技术分享原创和收集

发表评论 (审核通过后显示评论):