HTML 文档渲染过程
- 浏览器有多个进程,其中渲染进程被称为浏览器内核,负责页面解析、渲染和执行 JS 脚本等。
- 渲染进程内部有
JS 引擎线程、GUI 渲染线程、事件循环管理线程、定时器线程、HTTP 线程。 - JS 引擎线程负责执行 JS 脚本、GUI 渲染线程负责页面的解析和渲染( 如 HTML/CSS 的解析 )。
- 所以,JS 和 GUI 两者是互斥的、阻塞的,也就是执行 JS 时,页面(GUI)是停止解析和渲染的。这是因为如果在页面渲染的同时 JS 修改了页面元素,比如清空页面,会造成后续页面渲染的不必要和错误。
- 由于 JS 操作要经常操作 DOM ,就涉及 JS 引擎线程和 GUI 渲染线程间通信,而线程通信是非常昂贵的,这也是造成 JS 操作 DOM 效率不高的原因。
CSS 文件和 JS 文件的下载是否会阻塞渲染?
CSS 阻塞
- CSS 文件的下载和解析不会阻塞 DOM 的解析,但是会阻塞 DOM 的渲染。因为 CSSOM Tree (CSS对象模型)要和 DOM Tree(文档对象模型)合成 Render Tree 才能绘制页面。
以下代码, test1 在 CSS 下载并解析完成时默认样式, test2 在 CSS 下载并解析完成之前不会显示。
test1
test2
- CSS 文件没下载并解析完成之前,后续的 JS 脚本不能执行。所以,在提前执行不操作 DOM 元素的 JS 时,可以把 JS 放到 CSS 文件之前。下面的 alert("ok")在 CSS 下载并解析完成之前不会弹出来:
JS 阻塞
- JS 文件的下载和解析会阻塞 GUI 渲染进程,也就是会阻塞 DOM 和 CSS 的解析和渲染。这是因为JS的执行可能会修改DOM结构,因此必须等待JS执行完毕才能继续解析 DOM。以下代码,JS 文件没下载并解析完成之前,后续的 HTML 和 CSS 无法解析:
<script src="xxxx">
<div>test</div>
</script>
- 将JS文件放在页面底部,JS 文件的下载会减少阻塞前面 HTML 和 CSS 解析:
<div>test</div>
<script src="xxxx">
</script>
test
总结
-
GUI 渲染线程会尽可能早的将内容呈现到屏幕上,并不会等到所有 HTML 都解析完成后再去构建和布局 Render Tree,而是解析完一部分内容就显示一部分内容,同时,可能还在通过网络下载其余内容。
-
文件的下载是不会被阻塞的,不管是 CSS 还是 JS 文件,浏览器的主线程会在页面解析前开启下载。所以就算在外部脚本执行前删除脚本,脚本也还是会下载。
-
在 HTML 文档渲染过程中,CSS 文件的下载不会阻塞 DOM 的构建,但会阻塞渲染。而 JS 文件的下载和执行会阻塞 DOM 的构建和渲染。因此,在前端开发中,优化 JS 文件的加载方式对于提高网页性能至关重要。
优化建议
-
将 JS 文件放在页面底部:将 JS 文件放在 HTML 文档的底部可以减少对页面渲染的阻塞,因为此时大部分 DOM 已经解析完成,可以减少等待时间。
-
使用 async 和 defer 属性:
- async 属性:异步加载 JS 文件,下载完成后立即执行,不会阻塞 DOM 解析,但可能会在DOM解析完成前执行。
- defer 属性:延迟加载 JS 文件,直到DOM解析完成后执行,不会阻塞 DOM 解析,适用于需要依赖 DOM 的操作。
通过以上方式,可以有效优化网页的加载速度和用户体验。