小白也能搞懂的从输入URL到页面显示的过程(下)
上一篇小白也能搞懂的从输入URL到页面显示的过程我们讲了从输入URL到获取到数据的过程 ,这一篇我们就继续唠唠获取到数据后怎么显示页面
其中整个步骤就分为解析,渲染; 解析式将数据解析为render树,然后将其渲染出来
![]()
一、浏览器的多进程多线程
想要比较好地了解后面的内容,我们得先来搞定浏览器进程
- 浏览器是多进程多线程的
😜浏览器多进程
- Browser进程:浏览器的主进程,负责协调,主控
- 插件进程:每种类型的插件对应一个进程,仅当使用该插件时才会创建,保证插件奔溃也不会对浏览器和页面造成影响
- 网络进程:负责发起和接收网络请求
- GPU进程:初衷是的为了实现3D CSS效果,只是后面网页,Chorme的UI界面有用GPU来绘制,这使得GPU称为浏览器的普遍需求
- 渲染进程:默认每一个Tab页面一个进程,互不影响,控制页面渲染,脚本执行,事件处理等
😜渲染进程
渲染进程它是多线程的
- GUI线程:负责绘制浏览器界面,解析HTML,CSS,构建DOM树和Render树,布局和绘制等,重排重绘也是在该线程执行
- JS引擎线程:也称为JS内核,负责处理JavaScript脚本程序,一个render进程中只有一个JS线程在运行JS程序
- 计时器线程:指的是
setInterval
和setTimeout
,因为JS引擎是单线程的,所以如果处于阻塞的话,计时器就不准啦,所以需要单独的线程 - 异步http请求线程:XMLHttpRequest连接后浏览器开的一个线程,比如请求有回调函数,异步线程就会将回调函数加入事件队列,等待JS引擎空闲执行
- 事件触发线程:主要用来控制事件循环
😜GUI线程和JS引擎线程
- 从他们的用途可以看到,我们主要了解的就是这两个
- 他们两者是互斥的,当JS引擎执行的时候GUI线程会被挂起,GUI更新会保存在一个队列中等到JS引擎空闲时立即被执行。所以如果JS执行时间过长时,就会导致渲染不连贯,这也就是后面说阻塞的原因啦
- 为什么互斥呢:因为JS是可以操纵DOM的,如果一边修改元素一边渲染页面的话(不互斥的情况下),那么渲染前后获得的元素数据可能就不一致了,所以为了防止出现不可预期的结果,他们两个需要为互斥关系
二、构建DOM树
- 浏览器不能直接理解和使用html,所以要先构建DOM树,将html转为浏览器认识的结构
- 构建DOM树的整个过程是自上而下的
🤪什么是DOM
- DOM的全称为:
Document Object Model
--‘文件对象模型’ - 是HTML文档的对象化描述,也是HTML元素与外界的接口
🤪解析流程
- 根据html规范对字符词法分析,将html字符解析为Tokens(标记)
- 对标记进行语法分析,转成DOM节点并定义属性和规则
- 根据节点对象关系按照顺序依次向解析栈添加,形成DOM Tree(根节点为Document)
🤪什么是标记
- 上述流程我们提到的第一步就是解析为标记,那么标记是什么呢
- 标记包括开始和结束标记,以及属性名和值
- 一起来研究一下这个简单代码块是如何解析为标记
<html lang="en">
<body>
dddbug
</body>
</html>
- 初始状态是数据状态。遇到字符
<
时,状态更改为“标记打开状态” - 接收一个
a-z
的字符会创建“开始标记”,状态更改为 “标记名称状态”,此期间接收到的每个字符都会添加到新的标记名称上 - 遇到
>
之后,会发送当前的标记,状态返回“数据状态”-----到这里处理完<html>
- 重复以上的操作 ----处理完
<body>
后处于数据状态 - 接着遇到文字
dddbug
中的d
字符时,将创建并发送字符标记,每个字符都会发送一个字符标记 - 直到遇到中的
<
状态又回到了我们第一步遇到过的 “标记打开状态” - 遇到
/
,会创建结束标记,状态更改为 “标记名称状态”,此期间接收到的每个字符都会添加到新的标记名称上 - 直到接收到
>
,然后发送新的标记,回到数据状态-----到这里处理完</body>
</html>
也会进行同样的处理
- 浏览器是有容错机制的,会修复一些无效的html结点(不同浏览器的表现不一样)
- 在一般情况下,标记化阶段处理的数据来自网络,但 也可以来自在用户代理中运行的脚本,例如使用
document.write()
API
🤪怎么判断DOM何时解析完成
要了解什么会阻塞DOM解析,我们得先了解什么时候DOM才能算完成
- HTML有三个生命周期事件:
DOMContentLoaded
– 浏览器完全加载了 HTML,并且构建了 DOM 树,但图片<img>
和样式表等外部资源可能尚未加载load
– 不仅加载了 HTML,还加载了所有外部资源:图像、样式等。beforeunload/unload
– 用户正在离开页面。
也就是说
DOMContentLoaded
事件在 DOM 树准备就绪时触发
🤪阻塞DOM解析
敲黑板!先回看一下浏览器线程互斥那里
- 首先可以看这篇文章最上面的图,DOM解析和CSS解析是异步的,不会互相干扰,因此CSS的加载不影响DOM树的构建
但是从另一个角度,CSS会阻塞JS,间接导致阻塞DOM解析
- 内联JS:
async
和defer
对于内联JavaScript
都是无效的所以是会阻塞的 - 普通的
<script src=""></script>
外联js,当解析器遇到<script>
标签的时候,浏览器会暂停对DOM的解析,会一直等到该<script>
加载并执行后,才会继续往下解析 - 带有
defer
属性的外联JavaScript<script defer src=""></script>
,看到这个图你也许会疑惑,这不是没有阻塞,一路都是绿怎么会算阻塞呢。但是我们之前提过,派发DOMContentLoaded
意味着DOM树构建完毕,而它其实是在DOM树构建完成与派发DOMContentLoaded
事件之间执行的,所以算阻塞型选手(加载不阻塞但是执行阻塞)
为什么async外联js不算阻塞型选手呢,因为它执行的时候不一定在触发
DOMContentLoaded
事件之前
三、构建CSS规则树
😝区分CSS规则树和CSSOM
- 在查找文章的时候我也解开了属于我的一个疑惑:
CSS Rules tree
和CSSOM
的关系:CSSOM
:CSS Object Model
,CSS对象模型CSSOM
是CSS Rules tree
的一个节点CSS Rules tree
和CSSOM tree
是完全相等的。也就是同一个概念的不同名字
😝构建流程
😝Other
- CSS 规则的选择器是从右向左匹配。从右向左匹配时,如果当前元素不匹配,就可以直接跳过了。如果从左向右,要等到匹配最后一个选择器才能知道当前元素是否匹配。显然从右向左匹配效率会更高、
关于CSS具体解析有兴趣的可以看看这一篇🤐,探究 CSS 解析原理 - 知乎 (zhihu.com)
四、构造Render树
🤔什么是Render树
- 浏览器使用
DOM 树
和CSS规则树
构建出Render Tree
。此时不像构建DOM树 一样把所有节点构建出来,浏览器只构建需要在屏幕上显示的部分,因此像<head>
或<meta>
这些标签就无需构建了。
visibility:hidden
使元素不可见,但该元素仍然占据布局中的空间(即它被渲染为一个empty box)display:none
将元素从渲染树中完全删除,使得该元素不可见并且不是布局的一部分
🤔构建步骤
- 浏览器从 DOM 树开始,遍历每一个“可见”节点
- 对于每一个"可见"节点,在 CSSOM 上找到匹配的样式并应用
五、布局和绘制
🤭布局
渲染树生成后,还是没有办法渲染到屏幕上,渲染到屏幕上需要各个节点的位置信息,这就需要布局来处理了
- 核心任务:确定呈现树中所有节点的宽度、高度和位置,以及确定页面上每个对象的大小和位置的过程
- 会从渲染树的根节点开始遍历,通过渲染树每一个节点的宽高,位置等信息,确定每个节点在页面上的确切大小和位置。
- 影响布局的因素有很多,比如布局类型(正常流式布局、Flex布局、Grid布局),盒模型(Content Box、Border Box)等
- 布局计算的具体过程非常复杂,有兴趣的同学可以自行研究(我无
🤭绘制
- 浏览器会遍历渲染树,绘制页面的像素信息
- 在绘制,浏览器将在布局阶段计算的每个框转换为屏幕上的实际像素。绘画包括将元素的每个可视部分绘制到屏幕上,包括文本、颜色、边框、阴影和替换的元素(如按钮和图像)
六、合成
🤨合成流程
- 浏览器主进程将默认图层和复合图层交给GPU进程,GPU进程再将各个图层合成(
conposite
),最后显示出页面 - 默认图层指的是普通文档流的元素(包括absolute这些脱离文档流的)
- 复合图层一般指的使用
动画执行
或者<video>
,<iframe>
,<canvas>
,<webgl>
等元素,也可以使用z-index
将层级高的元素变成复合图层
🤨原因
- GPU中,各个复合图层是单独绘制的,单独分配资源,这样的话,无论它图层内部发送何等复杂的变化,也不会影响默认图层的回流重绘
🤨如何开启GPU加速
- 3D transforms:
translate3d
、translateZ
等 video
、canvas
、iframe
等元素- 对
opacity
、transform
、fliter
、backdropfilter
应用了animation
或者transition
- 元素A有一个
z-index
比自己小的元素B,且元素B是一个合成层(换句话说就是该元素在复合层上面渲染),则元素A会提升为合成层
七、回归题的总结
看到这里了其实可以自己尝试总结一下
🥳 从输入URL到页面显示发生了什么
- 构建请求行
- 浏览器缓存,先强制缓存后协商缓存
- DNS解析,将域名转换为IP地址
- 建立TCP连接
- 发送HTTP请求
- 服务器处理请求并返回HTTP响应
- 关闭TCP连接
- 构建DOM树:将html解析成DOM树
- 构建CSS规则树:生成CSS规则树
- 构建render树:Web浏览器将DOM和CSSOM结合,并构建出渲染树
- 布局:计算出每个节点在屏幕中的位置
- 绘制:即遍历render树,并使用UI后端层绘制每个节点
- 合成: GPU进程再将各个图层合成,最后显示出页面
题外话
- 🥺感觉这个问题能涉及的东西是很多的,很多内容感觉都可以自成一篇文章,知识都是一层嵌套一层,写到每个点都想拓展点什么,当然太复杂的我也没去折腾,感觉收益不多,写的东西感觉都是曾经看过的面试题相关知识点不过也有一些没写的哈哈
- 例如第一大点,可以拓展常问的线程和进程的区别,也可以扩展问事件循环相关的
- 第二大点,可以常问的阻塞相关问题以及
async
和defer
的区别和作用,事件DOMContentLoaded
和load
的区别 - 第五大点,可以问回流,重绘的区别,以及如何优化去避免
- 第六大点,可以涉及相关优化,例如就是说隐式合成,以及动画的实现方式从而减少回流和重绘
转载自:https://juejin.cn/post/7147296074112335879