# 浏览器渲染相关
# 渲染
浏览器如何渲染页面:
浏览器的网络线程获取到HTML文档后,会生成一个渲染任务,交给渲染主线程的消息队列,通过事件循环开启渲染流程。
# 1) 解析 HTML - Parse HTML
将 HTML 通过 Parse HTML 生成两颗树(DOM树,CSSOM树),从而开放JS操作的能力。
解析过程:
浏览器会提前开启预解析线程,会下载外部的CSS和JS文件,同时也会解析CSS。
- 遇到 CSS 解析 CSS,遇到 JS 执行 JS。
- 如果解析到
link,主线程不会等待,会继续解析后面的HTML,所以CSS不会阻塞HTML的解析,解析CSS的任务交给预解析线程。
- 如果解析到
script,会停止解析HTML,等待JS完全下载完成后并且等待全局代码解析完成后,才能继续解析HTML(因为 JS 可能会修改当前的 DOM 树)。
- 最终生成 DOM 树和 CSSOM 树。
# 2) 样式计算
主线程会遍历整个DOM树,依次计算出每个节点的最终样式,生成一个带有样式的DOM树。
relow: 会更改CSSOM树,本质就是重新计算layout树,需要重新计算布局树,会引发 layout 以及后续的步骤。浏览器内部会对多次计算layout进行优化,改动属性造成的reflow是异步完成的,但是读取属性(document.clientWidth)会导致立即reflow。
# 3) 布局 - Layout
依次遍历DOM树的每个节点,生成Layout树,包含每个节点的宽高,相对包含块的位置等。
和DOM树的区别:有些属性display:none会显示在DOM树,而在布局树中不显示。类似的还有伪元素等。

# 4) 分层 - Layer
渲染主线程会用一套浮躁的策略对整个Layout树进行分层,分层的好处在于,将来某一个层改变后,仅会对该层进行后续处理,提示效率。
滚动条,层叠上下文,transform,opacity 等样式都会影响分层结果,也可以使用will-change属性更大程度影响分层结果。
# 5) 绘制 - Paint
渲染主线程会为每个层单独产生绘制指令集,用于描述这一层的内容该如何画出来。 这个时候主线程结束。
repaint: 和几何信息无关,比如color:red,会重新根据分层新计算绘制指令。
# 6) 分块 - Tiling
合成线程会从线程池中取得一些分块线程,对每个图层进行分块,将其划分成更多的小区域。
# 7) 光栅化 - Raster
GPU进程处理,最终将每个块变成位图,优先处理靠近视口的块,这个过程会用到GPU加速。
# 8) 画 - Draw
最后一步就是画,合成线程拿到每个层,每个块的位图后,生成一个个指引(quad)信息。
指引会标识出每个位图应该画到屏幕的哪个位置,以及会考虑旋转,缩放等变形。
变形发生在合成线程,和渲染主线程无关,这就是transform效率高的本质原因。
合成线程把指引交给 GPU 进程,产生系统调用,交给 GPU 硬件,完成最终的屏幕成像。
# document.write
Chrome 下:
<body style="background-color:white;">
<div>header</div>
<script>
document.write("<p>body</p>");
</script>
<div>footer</div>
</body>
浏览器在解析的时候,遇到了script标签,则会执行,将p里面的内容渲染到文档流,也就是footer的前面,之后会浏览器会继续解析文档流,渲染p标签的内容,最终的展示元素:
<div>header</div>
<script>
document.write('<p>body</p>')
</script><p>body</p>
<div>footer</div>
如果在脚本中加入setTimeout,页面中只会有body内容,其余的都会被清空,究其原因是文档流在这个时候已经关闭,当执行document.write的时候会重新调用document.open打开新的文档流,document.open会清楚已有文档,所以会清除之前的内容。同理DOMContentLoaded和onload事件也会出现一样的情况。
<body style="background-color:white;">
<div>header</div>
<script>
setTimeout(() => {
document.write("<p>body</p>");
});
</script>
<div>footer</div>
</body>
比如:
document.writeln('
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.7.1/jquery.js"></script>
'); window.onload = function() { console.log($); };
这个时候就会生成script标签,并且在加载完成之后触发执行console.log。
总结:所以如果要用document.write进行载入(不是重写),需要在onload之前执行。在已经加载完的页面中调用document.write会重写页面。
← meta