说说 HTML 文档渲染过程,CSS 文件和 JS 文件的下载是否会阻塞渲染?

200 阅读3分钟

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 下载并解析完成之前不会显示。

Snipaste_2024-11-16_11-07-07.png

test1

test2
  • CSS 文件没下载并解析完成之前,后续的 JS 脚本不能执行。所以,在提前执行不操作 DOM 元素的 JS 时,可以把 JS 放到 CSS 文件之前。下面的 alert("ok")在 CSS 下载并解析完成之前不会弹出来:

Snipaste_2024-11-16_11-09-33.png

JS 阻塞

  • JS 文件的下载和解析会阻塞 GUI 渲染进程,也就是会阻塞 DOM 和 CSS 的解析和渲染。这是因为JS的执行可能会修改DOM结构,因此必须等待JS执行完毕才能继续解析 DOM‌。以下代码,JS 文件没下载并解析完成之前,后续的 HTML 和 CSS 无法解析:

Snipaste_2024-11-16_11-16-29.png

<script src="xxxx">
  <div>test</div>
</script>
  • 将JS文件放在页面底部,JS 文件的下载会减少阻塞前面 HTML 和 CSS 解析: Snipaste_2024-11-16_11-20-04.png
<div>test</div>
<script src="xxxx">
</script>

test

总结

  1. GUI 渲染线程会尽可能早的将内容呈现到屏幕上,并不会等到所有 HTML 都解析完成后再去构建和布局 Render Tree,而是解析完一部分内容就显示一部分内容,同时,可能还在通过网络下载其余内容。

  2. 文件的下载是不会被阻塞的,不管是 CSS 还是 JS 文件,浏览器的主线程会在页面解析前开启下载。所以就算在外部脚本执行前删除脚本,脚本也还是会下载。

  3. 在 HTML 文档渲染过程中,CSS 文件的下载不会阻塞 DOM 的构建,但会阻塞渲染。而 JS 文件的下载和执行会阻塞 DOM 的构建和渲染。因此,在前端开发中,优化 JS 文件的加载方式对于提高网页性能至关重要。

优化建议

  1. 将 JS 文件放在页面底部‌:将 JS 文件放在 HTML 文档的底部可以减少对页面渲染的阻塞,因为此时大部分 DOM 已经解析完成,可以减少等待时间‌。

  2. 使用 async 和 defer 属性‌:

    • async 属性‌:异步加载 JS 文件,下载完成后立即执行,不会阻塞 DOM 解析,但可能会在DOM解析完成前执行‌。
    • defer 属性‌:延迟加载 JS 文件,直到DOM解析完成后执行,不会阻塞 DOM 解析,适用于需要依赖 DOM 的操作‌。

通过以上方式,可以有效优化网页的加载速度和用户体验。