页面加载时会被 JS 和 CSS 阻塞吗?

「这是我参与2022首次更文挑战的第5天,活动详情查看:2022首次更文挑战」。

之前写了两篇文章,涉及到了页面访问整个过程的一些分析,比如页面生命周期的介绍页面访问时渲染过程中 HTML、JS 的关系,前面两篇只是抓住了 JS,没有囊括 CSS,并且在复现上没有明确给出工具,而今天这篇文章将使用 Chrome 的 Network 和 Performance 工具去分析整个页面的加载过程

  1. Network: 分析请求,文章中用于分析请求发送时序关系
  2. Performance: 分析页面加载性能,用于查看 HTML, CSS, JS 解析时序

前排提示,使用上面两种工具去分析时,请打开无痕模式并且关掉在无痕模式允许运行的插件,目的是为了避免插件脚本的加载影响后期的分析

CSS

就目前类似的文章中,都是在头部导入采用的延时去模拟 CSS 可能会阻塞 DOM 解析的效果,对于 CSS 这类静态资源来说,它们是由专门的下载线程来下载的,不会阻塞 GUI 线程(HTML 解析所在线程),但是 <link><script> 是同步请求,就是需要发送请求后需要等待响应,那么阻塞 HTML 的真正原因可能是 1. 同步请求因为延迟导致的阻塞,等返回响应后 HTML 就会解析 2. HTML 遇到头部的外联 CSS 样式后,文件太大导致的阻塞

为了搞清楚这两个点,我分别设置了延时和大 CSS 文件两种模拟方式,代码如下

<!DOCTYPE html>
<html>
  <head>
    <link rel="stylesheet" href="http://localhost:8000/500.css" />
  </head>

  <body>
    <p id="test">究竟什么时候解析到我呢?</p>
  </body>
</html>
复制代码
<!DOCTYPE html>
<html>
  <head>
    <link rel="stylesheet" href="http://localhost:8000/big.css" />
  </head>

  <body>
    <p id="test">究竟什么时候解析到我呢?</p>
  </body>
</html>
复制代码

延时模式下

img4.png

大文件模式下

img3.png

仔细看上面两个图蓝色虚线,也就是 DCL 所在的位置,全部都是在 CSS 加载开始一点点就完成了,对于上面的假设

  1. 同步请求阻塞
  2. 大文件阻塞

都没有影响到 HTML 的解析(注:外联样式 CSS 的请求实际上是在 Parse HTML 之前发生的)

由上面我们也可以知道,延时阻塞和大文件阻塞起到的效果是一致的,因此接下来的 demo 中都会采用延时的方案

位于头部的 CSS 会阻塞页面渲染

页面加载的过程中实际上是一行行读取 HTML,因此页面的内容也是一点点的打印/渲染出来,所以就有了 First Plain 的出现,含义为页面从开始加载到页面内容的任何部分在屏幕上完成渲染的时间,以今日头条的访问为例,当页面还在转圈时,页面内容其实就已经能够被看到大部分了,之所以还在转圈是因为图片等资源还没有下载完成,而根据白屏时间的定义不就是 First Plain 之前的时间吗?

CSS 阻塞页面渲染,为了避免页面重复渲染(比如 红 -> 绿 -> 白 -> ...),页面会等待 CSS 的下载完成,前面测试了延时和大文件 CSS 的加载,两张图中红色部分为 load,绿色为 First Plain,即页面第一次渲染,由此可见在头部的 CSS 阻塞页面的渲染,不会阻塞 DOM

凡事有例外

<link> 被置于某些元素的中间,就会阻塞 dom 的解析了,看看下面的例子

<!DOCTYPE html>
<html>
  <body>
    <p>究竟什么时候解析到我呢?</p>
  </body>
  <!-- 延时 500ms 发送 CSS 文件 -->
  <link rel="stylesheet" href="http://localhost:8000/500.css" />
</html>
复制代码

结果如下

img5.png

震惊!DOMContentLoaded 竟然在 Load 的后面(红线是 Load,蓝线是 DOMContentLoaded),至少在 Chrome 的 Performance 上给出了我们这样的答案

那么对于 HTML 的解析呢?看下图

img6.png

img7.png

HTML 解析的间隔时间出现了断层,也就是 CSS 的加载已经阻塞了 HTML 的解析,也就是 DOM 的解析,不同于在 <head> 标签的情况,DOMContentLoaded 需要等待 CSS 的完全加载,对于类似文章的观点,CSS 不会阻塞 DOM 的解析,在这个例子当中是错误的

也就是说,存在特例,当外联样式位于 DOM 元素尾部时,会阻塞位于 <link> 后紧跟的 HTML 的解析

JS 和 CSS

JS 的确阻塞 DOM 的解析,详细的内容可以看这篇文章,页面访问时渲染过程中 HTML、JS 的关系,那如果是 JS 和 CSS 一起呢?上面的分析给出了外联样式 CSS 放在头部和尾部是有不同的情况的,而没有 CSS 的情况下,把 JS 放在 HTML 任意部分都会阻塞 DOM 的解析,那么 CSS 混合 JS 就可以有以下 4 种情况

  1. 头部中引入外联样式 <head>...<link />...</head>
    1. JS 在 CSS 前 <head>...<script></script>...<link />...</head>
    2. JS 在 CSS 后
      1. <head>...<link />...<script></script>...</head>
      2. <head>...<link />...</head>......<script></script>
  2. 尾部中引入外联样式 <body></body>...<link />
    1. JS 在 CSS 前 <body></body>...<link />...<script></script>
    2. JS 在 CSS 前 <body></body>...<script></script>...<link />

例子如下

<!-- 1 -->
<!DOCTYPE html>
<html>
  <head>
    <script>
      console.log(document.querySelector("p"));
    </script>
    <link rel="stylesheet" href="http://localhost:8000/big.css" />
  </head>

  <body>
    <p id="test">究竟什么时候解析到我呢?</p>
  </body>
</html>
复制代码

第一种情况,JS 会阻塞 DOM 的解析,先执行 <scirpt> 的内容,无需等待 CSS 加载

<!-- 2 -->
<!DOCTYPE html>
<html>
  <head>
    <link rel="stylesheet" href="http://localhost:8000/big.css" />
    <script>
      console.log(document.querySelector("p"));
    </script>
  </head>

  <body>
    <p id="test">究竟什么时候解析到我呢?</p>
  </body>
</html>
复制代码

第二种情况,JS 需要等待 CSS 的加载完成才能够执行,因为 JS 可能需要获取到 CSS 中设置好的样式属性,比如宽度和高度等,那么后面的 DOM 就无法解析了,因此 CSS 加载 -> JS 等待 CSS 加载 -> JS 阻塞 DOM

img8.png

注意,补充一个小细节,看看下面这个例子

<!DOCTYPE html>
<html>
  <head>
    <link rel="stylesheet" href="http://localhost:8000/big.css" />
  </head>
  <body>
    <p id="test">究竟什么时候解析到我呢?</p>
  </body>
  <script>
    console.log(document.querySelector("p"));
  </script>
</html>
复制代码

同样是 CSS 在 JS 前,head 中的 JS 是获取不到 <p> 的,但 <body> 下面的是能获取到的,证明虽然 DOM 的解析被阻塞了,但是已经被解析出来的 DOM 是不影响操作的

又是一个小细节

<!-- 3 -->
<!DOCTYPE html>
<html>
  <body>
    <p id="test">究竟什么时候解析到我呢?</p>
  </body>
  <script>
    console.log(document.querySelector("p"));
  </script>
  <link rel="stylesheet" href="http://localhost:8000/500.css" />
</html>
复制代码

第三种情况,同第一种情况,无需等待 CSS 的加载,但是 DOMCOntentLoaded 需要等待 CSS 的加载

<!-- 4 -->
<!DOCTYPE html>
<html>
  <body>
    <p id="test">究竟什么时候解析到我呢?</p>
  </body>
  <link rel="stylesheet" href="http://localhost:8000/500.css" />
  <script>
    console.log(document.querySelector("p"));
  </script>
</html>
复制代码

第四种情况,同第二种情况,需等待 CSS 的加载,但是 DOMCOntentLoaded 需要等待 CSS 的加载

总结

对于 JS ,在交互上,想要正确的操作 DOM,将 JS 放在 HTML 尾部,当解析到 JS 时,虽然没有触发 DOMCOntentLoaded,但是已经可以正确操作 DOM,减少加载时间,将 JS 异步化,对于外部 JS,采用添加 defer 属性或者 async 引入

而 CSS 在头部不会阻塞 DOM 的解析,但 JS 在 CSS 加载的这段时间是不能够操作 DOM 的

猜你喜欢

转载自juejin.im/post/7061249301350973476