# 从输入 URL 到页面展示,这中间发生了什么?越细越好

  • 多进程架构
  • IPC(Inter-Process Communication 进程间通信)
  1. 注重细节
  2. 从想当然到理所应当,V8 引擎及浏览器架构
  3. 全面考察前端是否具有完整计算机本科学习能力的代表题
    web 开发 + 网络 + 操作系统
  4. 如何规范的回答
    有条理,进程是主脉络

# 动手实操

  1. 浏览器 上网的 proxy,通过浏览器来代理我们访问网页
    可以当搜索框来用,使用默认的搜索引擎
    url
    juejin.im 补全为 https://juejin.im/ 用户体验做的很好,补全协议
  2. 浏览器中,操作系统里的进程
    细节,像代码架构分层,流程细化
    web 访问,浏览器 chrome 多进程的架构模式,最流畅,用更多的内存,比 IE 优秀
    打开一个页面 至少有 4 个进程
    <!-- 主进程 --- 管家,chrome 浏览器进程
    子进程:
    • GPU 进程 --- 渲染进程,GPU 加速 --- 3D 渲染,css transform3d
    • NetWork Service
    • 标签页 -->

# 前提回答

chrome 多进程架构带来现代浏览器的快速访问体验,chrome 就是代表

  • 浏览器进程 并发
    • 启动浏览器,提供交互(url 输入)
    • 子进程管理(进程间通信 IPC)
    • 文件存储功能 --- 文件缓存 cookie localStorage... BOM
  • 渲染进程
    将请求回来的 html、css、js、图片等,解析成为可交互的页面
    渲染进程所有的内容都是通过网络获取的,会存在一些恶意代码利用浏览器漏洞对系统进行攻击,所以运行在渲染进程里面的代码是不被信任的。这也是为什么 Chrome 会让渲染进程运行在安全沙箱里,就是为了保证系统的安全
  • 网络进程
    面向渲染进程和浏览器进程等提供网络下载功能
  • 标签页

把 url 给网络进程,拿到数据后给渲染进程,涉及进程间的通信
并行执行

# 导航执行过程

访问过程,问题回答清楚,把执行流程解释清楚,进程间的流程

  1. 浏览器进程接收到用户输入的 URL 请求时,在主进程上,IPC 进程间通信将 URL 交给网络进程

  2. 网络进程中发起真正的 URL 请求,请求是由 c++ 模块
    2.1 request
    2.2 response

  • DNS 解析
    DNS(Domain Name System) 服务器 domain 域名 -> 获取服务器 IP 地址,如果请求协议是 HTTPS,那么还需要建立 TLS 连接
  • 网络协议 TCP HTTP
    首先,网络进程会查找本地缓存是否缓存了该资源
    如果有缓存资源,那么直接返回资源给浏览器进程
    如果在缓存中没有查找到资源,那么直接进入网络请求流程,浏览器提供了 DNS 数据缓存功能。即如果一个域名已经解析过,那会把解析的结果缓存下来,下次处理直接走缓存,不需要经过 DNS 解析
  • 接下来就是利用 IP 地址和服务器建立 TCP 连接
    通过三次握手(即总共发送3个数据包确认已经建立连接)建立客户端和服务器之间的连接
    连接建立之后,浏览器端会构建请求行、请求头等信息,并把和该域名相关的 Cookie 等数据附加到请求头中,然后向服务器发送构建的请求信息
  • 服务器接收到请求信息后,会根据请求信息生成响应数据(包括响应行、响应头和响应体等信息),并发给网络进程
  • 数据传输完成之后通过四次挥手断开连接
  1. 网络进程 响应头 数据(响应:响应头 + 响应体) 通知渲染进程开始准备干活
    过程:
    text/html text/json image/jpg 提前通知渲染? 把响应头数据解析,转发给浏览器进程
    网络进程收到后解析响应头:
* 网络进程接收到服务器发送的响应行和响应头之后,就开始解析响应头的内容,如果发现返回的**状态码是 301 或 302** 的时候,说明服务器需要浏览器重定向到其他 URL,这时网络进程会从响应头的 Location 字段里面读取重定向的地址,然后再发起新的 HTTP 或者 HTTPS 请求,一切又重头开始了  
* **如果 Content-Type 字段的值被浏览器判断为下载类型**,那么该请求会被提交给浏览器的下载管理器,同时该 URL 请求的导航流程就此结束  
* **如果是 HTML**,浏览器进程(主进程)接收到网络进程的响应头消息之后**提交文档消息(CommitNavigation)到渲染进程**,告诉它要准备接收数据  
  1. 渲染进程收到提交文档信息后,开始准备接收 HTML 数据,直接和网络进程建立数据管道

  2. body 到了,因为已经建立了数据管道,所以数据会直接到渲染进程,渲染进程进行渲染,等文档数据传输完成之后,渲染进程会返回确认提交的消息给浏览器进程

  3. 浏览器进程在收到确认提交的消息后,会更新浏览器界面状态,包括了安全状态、地址栏的 URL、前进后退的历史状态,并更新 Web 页面,这也是为什么在浏览器的地址栏里面输入了一个地址后,之前的页面没有立马消失,而是要加载一会儿才会更新页面

  4. 一旦文档被提交,渲染进程便开始页面解析和子资源加载,进入渲染阶段

# 渲染阶段

渲染过程是一个流水线操作,在执行过程中被划分成很多子阶段
按照渲染的时间顺序,流水线可分为如下几个子阶段:构建 DOM 树、样式计算、布局阶段、分层、绘制(绘制列表)、分块、光栅化和合成

  1. 构建 DOM 树
    浏览器无法直接理解和使用 HTML,所以需要将 HTML 转换为浏览器能够理解的结构 —— DOM 树
  2. 样式计算
    CSS 样式来源
    通过 link 引用的外部 CSS 文件<style>标记内的 CSS
    元素的 style 属性内嵌的 CSS
    格式化样式表
    把 CSS 转换为浏览器能够理解的结构
    当渲染引擎接收到 CSS 文本时,会执行一个转换操作,将 CSS 文本转换为浏览器可以理解的结构 —— styleSheets
    样式属性标准化
    有一些 CSS 样式的数值并不容易被渲染引擎所理解,因此需要在计算样式之前将它们标准化,如 em -> px, red -> #ff0000, bold -> 700 等,需要将所有值转换为渲染引擎容易理解的、标准化的计算值
    计算出 DOM 树中每个节点的具体样式
    两个规则: 继承和层叠
* 每个子节点都会默认继承父节点的样式属性,如果父节点中没有找到,就会采用浏览器默认样式,也叫 UserAgent 样式  
* CSS 最大的特点在于它的层叠性,也就是最终的样式取决于各个属性共同作用的效果  
在计算完样式之后,所有的样式值会被保存到 window.getComputedStyle 中  
  1. 布局阶段
    有了 DOM 树和 DOM 树中元素的样式之后,还要知道 DOM 元素的几何位置信息
    布局树仅包含可见元素,对于 head 标签和设置了 display: none 的元素,将不会被放入其中
    创建布局树
- 遍历生成的 DOM 树**可见节点**,并把他们添加到布局树中  
- 计算布局树节点的坐标位置  
  1. 分层
    页面中可能会有复杂的效果,如一些复杂的 3D 变换、页面滚动,渲染引擎还需要为特定的节点生成专用的图层,并生成一棵对应的图层树
    显式合成
- 拥有层叠上下文的节点  
  HTML根元素本身就具有层叠上下文。
  普通元素设置 position 不为 static 并且设置了 z-index 属性,会产生层叠上下文  
  元素的 opacity 值不是 1  
  元素的 transform 值不是 none  
  元素的 filter 值不是 none  
  元素的 isolation 值是isolate  
  will-change指定的属性值为上面任意一个  
- 需要裁剪的地方  
  比如一个div,你只给他设置 100 * 100 像素的大小,而你在里面放了非常多的文字,那么超出的文字部分就需要被剪裁。如果出现了滚动条,那么滚动条会被单独提升为一个图层  
**隐式合成**  
层叠等级低的节点被提升为单独的图层之后,那么所有层叠等级比它高的节点都会成为一个单独的图层  
如果在一个大型应用中,当一个z-index比较低的元素被提升为单独图层之后,层叠在它上面的的元素统统都会被提升为单独的图层,可能会增加上千个图层,大大增加内存的压力,甚至直接让页面崩溃。这就是层爆炸的原理  
  1. 生成图层绘制列表
    把一个图层的绘制拆分成很多小的绘制指令,然后再把这些指令按照顺序组成一个待绘制列表
  2. 栅格化(raster)操作
    在渲染进程中绘制操作是由渲染引擎中的合成线程来完成的,当绘制列表准备好之后,主线程会把这个列表提交给合成线程
    分块
    视口范围有限,但页面可能很大,所以合成线程会将图层划分为图块,通常是 256 * 256 或者 512 * 512,可以大大加速页面的首屏展示(首屏展示低分辨率的图片,等正常的图块内容绘制完成之后,替换图片)
    生成位图
    合成线程会按照视口附近的图块来优先生成位图,实际生成位图的操作是由栅格化来执行的。所谓栅格化,是指将图块转换为位图
    生成位图的过程实际上都会使用 GPU 进行加速,生成的位图最后发送给合成线程
  3. 合成和显示
    一旦所有图块都被光栅化,合成线程就会生成一个绘制图块的命令—— "DrawQuad",然后将该命令提交给浏览器进程
    浏览器进程里面有一个叫 viz 的组件,用来接收合成线程发过来的 DrawQuad 命令,然后根据 DrawQuad 命令,将其页面内容绘制到内存中,最后再将内存显示在屏幕上。
Last Updated: 5/6/2020, 11:48:16 AM