2020-12-21补充:《深入理解现代浏览器》对本文有直接指导。
通过之前的解释,我们已经(大概)知道了一个web请求发出去会经历怎样的心路历程。
在本篇会尝试解释浏览器收到了返回数据(特指html结构),大概会发生什么。也就是:浏览器如何工作,怎么渲染页。
前置知识
- browser process
- ui thread
- network thread
- renderer process
- main thread
- worker thread
- compositor thread
- raster thread
核心思路
这里都是 renderer process 里的 main thread 做的:
- Parse HTML :构建DOM 树。把字符串照规则 token 词法分析变成node节点,然后变成 dom树。Document Object Model
- subresource loading加载子资源,先扫描一遍
- js can block the parsing遇到script标签,会停止解析HTML,下载、解析、执行,会阻塞DOM树生成。当然了这里有 defer/async 也有preload
- 计算样式 parse stylesheet,并行构建CSSOM 树: css Object Model Tree 并标准化,也就是比如rem em转为px,white red转成rgb,不写样式也有默认样式
- 布局Layout,把DOM树和CSSOM树合成一个 Layout Tree ,这里不包含 none 的元素,添加伪元素
- Update Layer Tree
- 绘制Paint,确定顺序paint record,比如z轴的互相遮盖。
- 先分层再栅格化。合成线程把layer分成tiles块,交给栅格线程raster thread——栅格化块——存到GPU的内存里
- 合成compositing,合成线程compositor thread 收集要绘制的块信息draw quads,创建合成帧compositor frame。
- 合成帧通过gpu绘制到屏幕上。
Composite Layers
这里注意 dom tree 和 CSSOM Tree 是并行的,因此我们建议把css放到head里,尽可能早解析
细枝末节
1 页面为何白屏
浏览器的js引擎和渲染引擎都可以操作DOM,会造成资源的竞争,因此某一时刻只有一个线程操作DOM,也就是js引擎和渲染引擎会相互阻塞。
如果script里的js耗时长,会阻塞页面渲染。也就是会导致页面白屏。因此一般
- 把js放页面底部,
- async/defer 延迟执行js 都会立即下载
- async并发获取js,得到之后立即执行,无需考虑顺序。但执行js依然会阻塞。动态创建script默认是async=true
- defer延迟js执行,等到HTML文档解析之后,在DOMContentLoaded之前,再执行
- 有些耗时js可以放到sw里执行
2 Layout 和 Paint 关系
如何从技术上看到 绘制Paint和布局Layout的关系?
我们书写一个html,里面有1000个tag标签,写一点简单的样式。通过 devTools 里的 Performance - Bootom-Up 可以看到下面的图片:
可以看到,Layout耗时8.3ms,Paint耗时0.5ms
有了这个基础数据,我们通过js修改样式,修改margin值,这显然会重新计算布局,也就是 ReLayout
这个是先渲染,然后执行js得到的结果。我们可以看到 Layout耗时增加, Paint耗时增加。
接下来我们渲染完之后操作js修改字体颜色,得到下面的结果。可以看到 Layout几乎没有增加,这意味着RePaint机会不会影响Layout
但如果修改了字体,一样会触发ReLayout ,简单的结论是:
- 减少Layout操作
- Paint操作不一定会引起Layout
重排 reflow 也就是 re-layout
DOM中每个元素都有自己的盒子模型,需要浏览器根据样式来计算,放到该出现的位置,这个过程叫 reflow
- 增加删除修改 DOM节点,会导致reflow repaint
- 移动DOM的位置,或做动画
- 修改css样式
- resize窗口时候,或滚动时候
- 修改默认字体时候
重绘 repaint
等到各种盒子位置等属性确定之后,浏览器把元素绘制页面
- DOM 改动
- CSS改动
资源外链
css 资源
- css下载异步,不会阻塞构建DOM树
- 会阻塞渲染,构建render的时候会等到下载解析完毕后进行。
- 媒体查询不会阻塞渲染
js资源
- 阻塞浏览器的解析,等待js下载解析并执行后继续解析html
- defer和async,前者是延迟执行,后者是异步执行。
- async是异步执行,异步下载结束后就能执行,不保证执行顺序
- defer是延迟执行,效果看起来像放到body后面一样。
img
图片异步下载,不阻塞
loaded 和 domcontentloaded
- load 事件触发时候,页面上的DOM ,css,js,img都加载完了
- DOMContentLoaded 仅当DOM加载完成,不包括css,图片。
下一节: js运行机制