不会回答从输入url到页面显示发生了什么?这三张图教你

大前端 @ 转转

从用户输入 URL 到页面显示这中间浏览器做了什么,这应该算是一道非常经典的面试题了。不仅仅能考察面试者对于浏览器渲染原理的基本了解,还能从中延伸出很多的知识点和性能优化点。那今天我们就通过三张流程图,来重新捋清楚这题的思路吧。

第一张图--从输入 URL 到页面展示完整流程示意图

整体流程图

第一张图当然就是整体的流程图。图中也可以看出,这个问题的回答离不开浏览器进程相关的知识。所以,我们先来快速学习一下浏览器架构的简单知识。

目前的 Chrome 浏览器为多进程架构,包括 1 个浏览器主进程、1 个 GPU 进程、1 个网络进程、多个渲染进程和插件进程。

  • 浏览器进程主要负责界面显示、用户交互、子进程管理,同时提供存储等功能。
  • 渲染进程将 HTML、CSS 和 JavaScript 转换为用户可以与之交互的网页,排版引擎 Blink 和 JavaScript 引擎 V8 都运行在该进程中。
  • GPU进程用来实现 3D CSS 效果,绘制网页、Chrome 的 UI 界面。
  • 网络进程负责页面的网络资源加载。
  • 插件进程负责插件的运行。

有了浏览器架构的知识,再结合整体流程图,可以大致将流程描述如下。

  • 浏览器进程接收到用户输入的 URL 后,将 URL 转发给网络进程
  • 接着在网络进程中发起 URL 请求
  • 网络进程接收并读取响应头信息后将数据转发给浏览器进程
  • 浏览器进程接收到响应头数据后,发送提交导航信息到渲染进程
  • 渲染进程接到消息后,和网络进程建立数据管道,准备开始接收 HTML 数据
  • 接着渲染进程浏览器进程发送确认文档提交的消息
  • 浏览器进程接收到消息后,会移除旧文档,更新页面状态
  • 渲染进程也开始了页面解析和子资源加载,这些数据经过中间模块的处理,最终形成页面

这第一张图一开始看可能会有点迷茫,可以先大致瞅一眼过程,阅读完全文后再回头看也许会清晰很多。

第二张图--URL 请求流程图

URL请求流程图

从输入 URL 到页面显示这个过程发生了什么,具体可以分成导航流程渲染流程两部分来回答,每个部分也有若干子流程。

用户发出 URL 请求到页面开始解析的这个过程,叫做导航。

导航流程中最重要的部分就是 URL 请求流程。不过还是先来了解一下导航流程都有哪些具体步骤。

用户输入

首先,用户在地址栏输入关键字,地址栏会判断关键字是搜索内容还是请求的 URL,前者的话地址栏会使用浏览器默认的搜索引擎合成新的带关键字的 URL,后者则根据规则加上协议合成完整的 URL。

键入回车后,当前页面执行 beforeunload 事件。从图中可以看出,浏览器进入加载状态后,页面还是之前打开的内容,这时需要等待提交文档阶段,页面内容才会被替换。

URL 请求过程

接下来,浏览器进程会通过进程间通信(IPC)把 URL 请求发送至网络进程,网络进程接收到 URL 请求后,会经历下图的阶段。

URL请求流程图

在这里先呼应开头流程图中提到的重定向,如果服务端返回的响应头中状态码是 301 或者 302,那么网络进程会从响应头的 Location 字段里面读取重定向的地址,然后再发起新的 HTTP 或者 HTTPS 请求,否则浏览器继续处理该请求。

接着,浏览器会根据返回响应头中的 content-type 值判断响应体数据的类型,text/html 说明是 HTML 格式,浏览器会继续进行导航流程,而 application/octet-stream 则是字节流类型,浏览器会按照下载类型来处理请求。 由于 Chrome 的页面渲染是运行在渲染进程中的,所以接下来就需要准备渲染进程了。

准备渲染进程

Chrome 的默认策略是,每个标签对应一个渲染进程。但如果从一个页面打开了另一个新页面,而新页面和当前页面属于同一站点(根域名加上协议,还包含了该根域名下的所有子域名和不同的端口)的话,那么新页面会复用父页面的渲染进程。如下图所示。 渲染进程准备好之后,还不能立即进入文档解析状态,因为此时的文档数据还在网络进程中,并没有提交给渲染进程,所以下一步就进入了提交文档阶段。

提交文档

这是指浏览器进程网络进程接收到的 HTML 数据提交给渲染进程,具体流程如下:

  • 浏览器进程收到网络进程的响应头数据后,向渲染进程发起提交文档的消息
  • 渲染进程收到后,会和网络进程建立传输数据的管道
  • 数据传输完后,渲染进程会返回确认提交的信息给浏览器进程
  • 浏览器进程在收到消息后,会更新浏览器界面状态,包括安全状态、地址栏的 URL、前进后退的历史状态,并更新 Web 页面 更新后的浏览器状态栏如下图所示。

在这个环节中,深挖点和性能优化点应该都在 URL 请求阶段。 比如,TCP 协议是如何把数据包送达应用程序的,和 UDP 协议有什么不同?

首先由于 TCP 的特性,数据需要拆分成一个个数据包来回多次进行传输的,通常 1 个 HTTP 的数据包在 14KB 左右。下图就是简化的 TCP 网络四层传输模型。

  • 上层将含有“转转”的数据包交给传输层
  • 传输层会在数据包前面附加上 TCP 头,组成新的 TCP 数据包,再将新的 TCP 数据包交给网络层
  • 网络层再将 IP 头附加到数据包上,组成新的 IP 数据包,并交给底层
  • 数据包被传输到主机 B 的网络层,在这里主机 B 拆开 IP 头信息,并将拆开来的数据部分交给传输层
  • 在传输层,数据包中的 TCP 头会被拆开,并根据 TCP 中所提供的端口号,把数据部分交给上层的应用程序
  • 最终,含有“转转”信息的数据包就旅行到了主机 B 上层应用程序这里

TCP 协议提供重传机制和数据包排序机制,保证了数据传输的可靠性。

而对比之下,UDP 协议通过端口号就能把指定的数据包发送给指定的程序。流程和上图 TCP 协议大体一致,不过 UDP 协议只能校验数据的正确性,以及拥有很快的传输速度,但是不能保证数据的和可靠性和完整性。通常会应用在一些关注速度、但不那么严格要求数据完整性的领域,如在线视频、互动游戏等。

至于 TCP 三次握手和四次挥手的机制,感觉又可以写一篇文章了,看官们可以右转 Google 一下。

资源加载阶段的性能优化,关注点应该在减少需要加载关键资源的个数和关键资源的大小。而下一篇文章我们会介绍 HTTP 的优化手段,大家可以关注一下。

第三张图--渲染流程图

好咯下面是最后一 part。 渲染流程图

文档被提交后,导航流程就结束了,渲染进程便开始页面解析和子资源加载。

如上图所示,输入的是 HTML、CSS、JavaScript 数据,这些数据经过中间渲染模块的处理,最终输出为屏幕上的像素,这样的处理流程叫做渲染流水线

按照渲染的时间顺序,渲染流水线可以分成下面几个阶段:构建 DOM 树样式计算布局阶段分层绘制光栅化合成

接下来,我们就来详解图中的各个子阶段。

构建 DOM 树

由于浏览器无法直接理解和使用 HTML,所以需要由 HTML 解析器将 HTML 转换为浏览器能够理解的结构--DOM 树。

在浏览器中打印 document,可以看到一个完整的 DOM 结构,DOM 结构和 HTML 的内容几乎是一样的,不过 DOM 是保存在内存中的树结构,可以通过 JavaScript 来查找和修改其内容。

样式计算

有了 DOM 节点之后,需要有让节点拥有正确的样式。而同样,浏览器也无法理解纯文本的 CSS,所以当渲染引擎收到 CSS 文本时,会执行转换成 styleSheets 的操作。

在控制台打印 document.styleSheets 能看到样式结构的数据,该结构同时具备了查询和修改功能,这会为后面的样式操作提供基础。 然后,将样式表中的属性值转换成渲染引擎容易理解的、标准化的计算值。如 2em、blue、bold,需要转成 32px、rgb(0,0,255)、700。 最后是根据 CSS 的层叠规则和继承队则,计算 DOM 树中每个节点的具体样式。

布局阶段

使用 DOM 树和 DOM 树中的样式,构建只包含可见元素的布局树,并计算布局树节点的坐标位置。

分层

为了方便地实现页面中一些复杂的3D变换页面滚动,或者使用z-indexing做 z 轴排序,渲染引擎为特定的节点生成专用的图层,没有特定图层的节点就从属于父节点的图层,这些图层生成对应的图层树,并叠加构成了最终的页面图像。

绘制

图层中元素的前景、背景、边框都需要单独的指令去绘制,这些指令被按顺序组成一个待绘制列表。

光栅化

主线程把绘制列表交给合成线程,合成线程将图层划分为块,优先将视口附近的图块生成位图,位图被保存在GPU的内存中。

合成

一旦所有图块都被光栅化,合成线程就会生成一个绘制图块的命令提交给浏览器进程。浏览器进程里面有一个叫 viz 的组件,用来接收合成线程发过来的 DrawQuad 命令,然后根据 DrawQuad 命令,将页面内容绘制到内存中,最后再将内存显示在屏幕上。

到这里, HTML、CSS、JavaScript 等文件,经过浏览器就会显示出页面了。

在 DOM 解析过程中,如果遇到 script、link、style 等标签都有可能会阻塞这个过程。因为 JS 可能要修改当前生成的 DOM 结构,也有可能修改 CSSOM。简而言之。持续 DOM 的构建,依赖于 JS 的执行,JS 的执行依赖于 CSSOM 的构建。

这一部分自然也影响了首屏时间,所以 Chrome 浏览器提供了一些优化操作。当渲染引擎收到字节流之后,会开启一个预解析线程,用来分析下载 HTML 文件中包含的 JavaScript、CSS 等文件。

而我们也可以使用 CDN 来加速文件的下载,没有 DOM 相关操作的 JS 文件,可以通过 async 或 defer 标记来设置为异步加载。还可以使用 webpack 的 webpackChunkName 注释,将首屏组件和非首屏组件分开打包,缩短白屏展示时长。

此外,还有三个经常碰到然后又和渲染流程有关的概念,重排、重绘和合成。

通过 JavaScript 或者 CSS 修改了元素的几何属性比如宽度高度等,浏览器会触发布局以及之后的子阶段,这就是重排。结合渲染流程图也可以看出,重排需要更新完整的渲染流水线,所以开销很大。

如果只修改了元素的绘制属性如背景颜色等,会省去布局和分层阶段,直接进入 Paint 阶段,这就是重绘,执行效率会比重排要高。

合成的概念,则解释了为什么 CSS 动画比 JS 的动画要高效。例如如果使用 transform 来实现动画效果,渲染引擎会跳过布局和绘制两个阶段,在非主线程合成,这样便能提高绘制效率。

讲了不少啦,写着写着觉得给自己挖了好多个坑,每一个知识点感觉都可以延伸出一篇文章的样子。所以当被问到这个经典的面试题时,你知道要怎么准备和回答了么?

最后

最后,感谢您的阅读,有任何问题,欢迎评论区留言讨论,互相学习~ 本月我们将推出更多实践相关文章及有奖留言活动,也欢迎扫描下方二维码关注我们的微信公众号及时获取 ↓

参考资料

  1. 李兵--极客时间《浏览器工作实践与原理》
  2. How Browsers Work www.html5rocks.com/en/tutorial…
  3. HTTP 教程 developer.mozilla.org/zh-CN/docs/…
  4. Inside look at modern web browser developers.google.com/web/updates…
文章分类
前端
文章标签