浏览器渲染原理与阻塞
浏览器多进程架构
- 主进程/浏览器进程:负责界面显示、用户交互、子进程之间的调度和存储
- 渲染进程:主要负责将
HTML、CSS、JS转换成用户可以访问的网页。GUI线程(重要)- 负责浏览器界面的渲染,
html解析、CSS解析、dom树、render树、分层树的构建 - 回流和重绘
- `GUI`和`JS`线程二者互斥,一个执行另外一个就会别挂起
- 负责浏览器界面的渲染,
JS引擎线程(重要)- 负责处理
JS脚本程序、JS垃圾回收、任务队列
- 负责处理
- 事件触发线程
- 负责维护一个事件队列
- 定时器触发线程
- 负责处理计时器。
JS线程执行代码是解析代码后执行,JS阻塞可能会影响定时器的准确性,所以需要一个单独的线程处理计时器工作- 在浏览器中
setTimeout的最低触发时间间隔为4ms,即使定时器的时间写的是0也不可能做到真的0
- 异步
http请求线程- 在
XMLHttpRequest连接后,浏览器新开一个线程 - 检测到状态变更时,回调函数会放在异步队列等待
JS线程执行
- 在
- 网络进程:负责网络请求
GPU进程:绘制界面的特殊效果- 插件进程:负责插件,防止插件崩溃影响页面
浏览器何将HTML渲染到页面上
-
关键渲染路径
五个步骤:1.构建
DOM(文档对象模型)、2.构建CSSOM(CSS对象模型)、3.构建render树、4.布局、5.绘制 -
每个步骤做的事情
-
构建
DOM-
转化成字符串,根据文件编码如
UTF-8将字节转换字符串 -
分词得到
Token,将如<html>,<body>以及尖括号内的其他字符串 -
Token转换为Node,将token转换成对象,包含了属性和方法 -
构造
DomTree,将上一步的各个节点的关系构造得到一个树形的DomTree,最终输出文档对象模型DOM
注意:
- 写在
html标签中的行内样式是在html parser解析的 html解析并不等整个HTML文档加载完才开始的,而是网络进程一边加载,HTML解析器一边解析。
-
-
构建
CSSOM内联
CSS和外部CSS样式表在此处解析。具体步骤和上面类似:字节→字符串→
Token→Node→输出CSSOM细节:CSS Parse和Dom Parse是并行关系吗?
-
style标签中的样式,会被html parser解析,不存在这个说法。(实际中较少用) link外链的CSS文件,-
遇到
link标签,网络进程加载CSS的时候,不会阻塞html的解析,这里是并行的。 - 当网络进程加载完样式文件后,
CSS parser会和html parser抢占主线程(同一时间只能进行一个操作)
- 加载:同步(非异步)
JS文件的加载会阻塞html的解析,而CSS文件的加载不会阻塞html解析。 - 解析:
CSS和JS的解析都会阻塞dom/html解析
-
-
生成渲染树
-
从
dom Tree开始遍历每一个可见节点不可见节点:
display:none、脚本标签script、元标签head -
对于
dom Tree上的每个可见节点,在CSSom中找到对应的规则 -
最终在
Render Tree上挂在带有样式的可见节点。Render Tree上包含页面所有可见内容和样式信息。
-
-
布局Layout
-
计算每个节点在屏幕上的确切位置和大小
-
所有相对测量值都会转换成屏幕上的绝对像素
-
-
绘制Paint
最终浏览器呈现出
HTML渲染完毕的结果。
-
JavaScript的加载与执行是否会阻塞页面渲染
谷歌浏览器做了很多优化,其中一个优化就是预解析操作。当渲染引擎收到字节流之后,会开启预解析线程,分析
HTML文件中包含的JS,CSS文件,解析到相关文件后,预解析线程会提前下载这些文件。会同时发起这些文件的下载请求。这两个文件的下载过程是重叠的,所以总的下载时间等于最长的文件下载时间。不管CSS和JS文件谁先到达,都等到CSS文件下载完成并生成CSSOM,然后执行JavaScript脚本。
JS脚本分为不同情况,
- 写在
script标签中的内部脚本,内联JS - 通过
src加载的外部文件,外部JS
以及JS的加载或执行也会有不同的表现。
注意:不管是JS和加载还是解析,如果阻塞页面渲染,只会影响后续
DOM的解析和渲染,并不会影响脚本之前的DOM的解析和渲染。
内联JS
- 操作:
- 内联
JS不存在加载操作,而它的解析和执行会阻塞后续页面的渲染。 - 遇到
JS脚本时,JS引擎会抢占渲染线程执行JS文件,导致无法渲染
- 内联
-
内联
JS放在底部原因:-
内联
JS会阻塞它后面DOM的渲染,放在底部不会阻塞渲染。 -
同时,放在底部可以拿到内存中已经构造好的
DOM节点进行DOM操作。
-
外链JS
前提:停止解析后续
DOM并不意味着一定会阻塞页面的渲染。
外链JS有加载和执行,同时加载又分为同步加载、异步加载(async/defer)。
同时还要区分外链JS所在的位置,顶部、底部。
-
加载
- 非异步加载的外链
JS- 外部
JS的加载可能花费很多时间,因此将JS放在底部,防止页面白屏。 - 放在后面还可以获取已有的
DOM
- 外部
-
使用
defer/async异步加载的外链JS-
使用
async/defer异步加载的文件不会阻塞后续DOM的解析-
async的文件在加载完就会执行,执行会阻塞页面渲染。 -
defer的文件执行会延迟到文档完成解析后,触发DOMContentLoaded事件前执行,有defer属性的脚本会阻止DOMContentLoaded事件,直到脚本被加载并且解析完成。
-
-
- 非异步加载的外链
-
执行
只要执行
JS线程势必会抢占主线程,阻塞后续的渲染。外部脚本链接的加载和执行只会影响后续 Dom 的解析和渲的染,对于脚本之前的的 Dom 并不会阻塞它的解析以及渲染。
CSS的加载与解析是否会阻塞页面渲染
-
CSS的加载既会也会阻塞DOM的渲染。-
普通情况下,
link标签的CSS文件会交给网络进程异步下载,不会阻塞Dom Tree的合成。 -
同时,如果页面中
CSS未下载完,因此未解析完,无法构建render tree,因此阻塞后续页面的渲染。 -
特殊i情况,如果
link后存在JS文件,那么CSS代码的加载会阻塞后续JS脚本的执行从而JS脚本会阻塞后续DOM解析,从而变相阻塞JS代码之后的DOM解析。
-
-
CSS的解析一定会阻塞DOM渲染,因为html parse和CSS parse二者是竞争状态。
参考文章
是JS的加载阻塞了页面的渲染还是JS的执行阻塞了页面的渲染?