输入URL到页面渲染的整个流程
1. 浏览器解析URL
首先会对 URL 进行解析,分析所需要使用的传输协议和请求的资源的路径。
- 如果输入的 URL 中的
协议或者主机名不合法,将会把地址栏中输入的内容传递给搜索引擎。 - 如果没有问题,浏览器会检查 URL 中是否出现了
非法字符, 如果存在非法字符,则对非法字符进行转义后再进行下一过程。
2. 浏览器缓存分析
(如果是相同的URL,浏览器会根据缓存机制决定是直接使用副本响应访问请求还是向源服务器再次发起请求。) 浏览器会判断所请求的资源是否在缓存里,如果请求的资源在缓存里并且没有失效,那么就直接使用,否则向服务器发起新的请求。
3.DNS查询,解析出IP
DNS查询是系统自己做的:
- 操作系统会在本地缓存查询IP;
- 没有的话会去系统配置的DNS服务器中查询;
Tips:DNS的核心系统是一个三层的树状、分布式服务,基本对英域名的结构。
- 如果还没有的话,会去DNS
根域名服务器查询,找出COM这个一级域名(顶级域名)服务器的IP地址;目前全世界共13组根域名服务器。 - 去该
顶级域名服务器查询google.com这个二级域名服务器的IP地址; - 去google.com这个
权威域名服务器查询自己域名下主机的IP地址,返回www.google.com 的IP地址。
DNS查询是基于UDP的。
默认端口号是53。
域名一定有一个ip,一个ip却不止一个域名。 浏览器的DNS缓存,chrome对每个域名会默认 60s,IE 30min,firefox 1min,safari 10s。
DNS 为什么使用 UDP 协议作为传输层协议?
为了避免使用 TCP 协议时造成的连接时延。因为为了 得到一个域名的 IP 地址,往往会向
多个域名服务器查询,如果使用 TCP 协议,那么每次请求都会存在连接时延,这样使 DNS 服务变得很慢,因为大多数的地址查询请求,都是浏览器请求⻚面时发出的,这样会造成网⻚的等待时间过⻓。
4. 分层处理报文
- 应用层会下发数据给传输层,这里 TCP 协议会指明
源端口号和目的端口号,然后下发给网络层; - 网络层中的 IP 协议会确定 IP 地址,将本机地址作为源地址,将IP地址作为目的地址,下发给数据链路层;
- 数据链路层的发送需要加入通信双方的 MAC 地址,我们本机的 MAC 地址作为源 MAC 地址,目的 MAC 地址需要分情况处理。
通过将 IP 地址与我们本机的子网掩码相与,我们可以判断我们是否与请求主机在同 一个子网里,如果在同一个子网里,我们可以使用 APR 协议获取到目的主机的 MAC 地址,如果我们不在一个子网里,那么 我们的请求应该转发给我们的网关,由它代为转发,此时同样可以通过 ARP 协议来获取网关的 MAC 地址,此时目的主机 的 MAC 地址应该为网关的地址。
- 通信前 TCP 握手,建立连接
- 通信前HTTPS会进行 TLS 握手。
5.服务器响应
- 数据在进入服务端之前,可能还会先经过负责负载均衡的服务器,它的作用就是将请求合理的分发到多台服务器上,这时假设服务端会响应一个 HTML 文件。
6.浏览器接收响应
-
首先浏览器会判断状态码是什么,如果是 200 那就继续解析,如果 400 或 500 的话就会报错,如果 300 的话会进行重定 向,这里会有个重定向计数器,避免过多次的重定向,超过次数也会报错。
-
浏览器开始解析文件,如果是 gzip 格式的话会先解压一下,然后通过文件的编码格式知道该如何去解码文件。
7. 组织渲染树
Critical Rendering Path,中文翻译过来,叫做关键渲染路径。指的是浏览器从请求 HTML,CSS,JavaScript 文件开始,到将它们最终以像素输出到屏幕上这一过程 文件解码成功后会正式开始渲染流程
下面的过程并不是依次进行的,而是存在一定交叉
-
构建 DOM
- 将 HTML 解析成许多 Tokens
- 将 Tokens 解析成 object
- 将 object 组合成为一个 DOM 树
这个过程是
循序渐进的,我们假设 HTML 文件很大,一个RTT (Round-Trip Time,往返时延) 只能得到一部分,浏览器得到这部分之后就会开始构建 DOM,并不会等到整个文档就位才开始渲染。这样做可以加快构建过程,而且由于自顶向下构建,后面的构建不会对前面造成影响。
如果遇到 script 标签 的话,会判断是否存在 async 或者 defer ,前者会并行进行下载并执行 JS,后者会先下载文件,然后等待 HTML 解析完成后顺序执行。如果以上都没有,就会阻塞住渲染流程直到 JS 执行完毕。遇到文件下载的会去下载文件,这里如果使用 HTTP/2 协议的话会极大的 提高多图的下载效率 -
构建 CSSOM
- 解析 CSS 文件,并构建出一个 CSSOM 树(过程类似于 DOM 构建)
当 HTML 解析中遇到
<link>标签时,会请求对应的 CSS 文件,当 CSS 文件就位时便开始解析它(如果遇到行内<style>时则直接解析),这一解析过程可以和构建 DOM 同时进行。
CSSOM 则必须等到所有字节收到才开始构建。如果是外部样式,CSSOM 的构建必须要获得一份完整的 CSS 文件,而不像 DOM 的构建是一个循序渐进的过程。因为 CSS 文件中包含大量的样式,后面的样式会覆盖前面的样式,如果我们提前就构建 CSSOM,可能会得到错误的结果。 -
构建 Render Tree
- 结合 DOM 和 CSSOM 构建出一颗 Render 树
浏览器只构建需要在屏幕上显示的部分,因此像
<head>,<meta>这些标签就无需构建了。同时,对于display: none的元素,也无需构建构建过程遵循以下步骤: 1. 浏览器从 DOM 树开始,遍历每一个“可见”节点。 2. 对于每一个"可见"节点,在 CSSOM 上找到匹配的样式并应用。 3. 生成 Render Tree。 -
Layout
- 计算出元素相对于 viewport 的相对位置
目前为止,我们已经拿到了元素相对于 Viewport 的详细信息,所有的值都已经计算为相对 Viewport 的精确像素大小和位置,就差显示了。
-
Paint
- 将 render tree 转换成像素,显示在屏幕上
浏览器将每一个节点以像素显示在屏幕上,最终我们看到页面。
7.1 引入 JavaScript
JavaScript 却是网页中不可缺的一部分,这里对它如何影响 CRP 做一个概要
- 解析 HTML 构建 DOM 时,遇到 JavaScript 会被阻塞
- JavaScript 执行会被 CSSOM 构建阻塞,也就是说,JavaScript 必须等到 CSSOM 构建完成后才会执行
- 如果使用异步脚本,脚本的网络请求优先级降低,且网络请求期间不阻塞 DOM 构建,直到请求完成才开始执行脚本
7.2 回流 reflow 和重绘 repaint
重绘:重新描绘某个区域。
回流:我们增删 DOM 节点,修改一个元素尺寸,页面布局和 DOM 树结构发生变化,肯定需要重新构建 DOM 树,而 DOM 树与渲染树是紧密相连的,DOM 树构建完,渲染树也会随之对页面进行再次渲染。
重绘:对 DOM 操作简单修改样式(比如修改元素的 visibility、color、background-color 等)、却并未影响页面布局时,浏览器不需重新计算元素的位置尺寸等,直接为该元素绘制新的样式。这个过程叫做重绘。
回流:对 DOM 操作导致 DOM 尺寸等属性的变化(比如修改元素的 width、height、top)时,浏览器需要重新计算元素的属性,然后再将计算的结果绘制出来,这个过程叫做回流。
常见的会导致回流的操作:
- 页面首次加载
- 浏览器窗口尺寸改变
- 元素尺寸或位置改变
- 元素内容变化
- 元素字体大小变化
- 增删 DOM 元素
- 查询或调用某些特定属性方法
总结:回流往往代价比重绘大;回流一定重绘,重绘未必回流。
7.3 浏览器多进程架构
在Chrome中,主要的进程有4个:
- 浏览器进程 (Browser Process):负责浏览器的TAB的前进、后退、地址栏、书签栏的工作和处理浏览器的一些不可见的底层操作,比如网络请求和文件访问。
- 渲染进程 (Renderer Process):负责一个Tab内的显示相关的工作,也称渲染引擎。
- 插件进程 (Plugin Process):负责控制网页使用到的插件
- GPU进程 (GPU Process):负责处理整个应用程序的GPU任务
这4个进程之间的关系是什么呢?
首先,当我们是要浏览一个网页,我们会在浏览器的地址栏里输入URL,这个时候
Browser Process会向这个URL发送请求,获取这个URL的HTML内容,然后将HTML交给Renderer Process,Renderer Process解析HTML内容,解析遇到需要请求网络的资源又返回来交给Browser Process进行加载,同时通知Browser Process,需要Plugin Process加载插件资源,执行插件代码。解析完成后,Renderer Process计算得到图像帧,并将这些图像帧交给GPU Process,GPU Process将其转化为图像显示屏幕
Renderer Process的作用是负责一个Tab内的显示相关的工作,这就意味着,一个Tab,就会有一个Renderer Process,这些进程之间的内存无法进行共享,而不同进程的内存常常需要包含相同的内容。
为了节省内存,Chrome提供了四种进程模式(Process Models),不同的进程模式会对 tab 进程做不同的处理。
- Process-per-site-instance (default) - 同一个 site-instance 使用一个进程
- Process-per-site - 同一个 site 使用一个进程
- Process-per-tab - 每个 tab 使用一个进程
- Single process - 所有 tab 共用一个进程
7.4 网页加载过程
之前我们我们提到,tab以外的大部分工作由浏览器进程Browser Process负责,针对工作的不同,Browser Process 划分出不同的工作线程:
UI thread:控制浏览器上的按钮及输入框;network thread:处理网络请求,从网上获取数据;storage thread: 控制文件等的访问;
7.4.1 第一步:处理输入
当我们在浏览器的地址栏输入内容按下回车时,UI thread会判断输入的内容是搜索关键词(search query)还是URL,如果是搜索关键词,跳转至默认搜索引擎对应都搜索URL,如果输入的内容是URL,则开始请求URL。
7.4.2 第二步:开始导航
回车按下后,UI thread将关键词搜索对应的URL或输入的URL交给网络线程Network thread,此时UI线程使Tab前的图标展示为加载中状态,然后网络进程进行一系列诸如DNS寻址,建立TLS连接等操作进行资源请求,如果收到服务器的301重定向响应,它就会告知UI线程进行重定向然后它会再次发起一个新的网络请求。
7.4.3 第三步:读取响应
network thread接收到服务器的响应后,开始解析HTTP响应报文,然后根据响应头中的Content-Type字段来确定响应主体的媒体类型(MIME Type),如果媒体类型是一个HTML文件,则将响应数据交给渲染进程(renderer process)来进行下一步的工作,如果是 zip 文件或者其它文件,会把相关数据传输给下载管理器。
与此同时,浏览器会进行 Safe Browsing 安全检查,如果域名或者请求内容匹配到已知的恶意站点,network thread 会展示一个警告页。
7.4.4 第四步:查找渲染进程
各种检查完毕以后,network thread 确信浏览器可以导航到请求网页,network thread 会通知 UI thread 数据已经准备好,UI thread 会查找到一个 renderer process 进行网页的渲染。
浏览器为了对查找渲染进程这一步骤进行优化,考虑到网络请求获取响应需要时间,所以在第二步开始,浏览器已经预先查找和启动了一个渲染进程,如果中间步骤一切顺利,当 network thread 接收到数据时,渲染进程已经准备好了,但是如果遇到重定向,这个准备好的渲染进程也许就不可用了,这个时候会重新启动一个渲染进程。
7.4.5 第五步:提交导航
到了这一步,数据和渲染进程都准备好了,Browser Process 会向 Renderer Process 发送IPC消息来确认导航,此时,浏览器进程将准备好的数据发送给渲染进程,渲染进程接收到数据之后,又发送IPC消息给浏览器进程,告诉浏览器进程导航已经提交了,页面开始加载。
这个时候导航栏会更新,安全指示符更新(地址前面的小锁),访问历史列表(history tab)更新,即可以通过前进后退来切换该页面。
7.4.6 第六步:初始化加载完成
当导航提交完成后,浏览器进程把数据交给了渲染进程,渲染进程开始加载资源及渲染页面,当页面渲染完成后(页面及内部的iframe都触发了onload事件),会向浏览器进程发送IPC消息,告知浏览器进程,这个时候UI thread会停止展示tab中的加载中图标。