腾讯云技术分享专栏 css 加载会造成阻塞吗

匿名 · 2019年02月12日 · 最后由 Lee 回复于 2019年02月14日 · 3003 次阅读

本文由云 + 社区发表

作者:嘿嘿嘿

可能大家都知道,js 执行会阻塞 DOM 树的解析和渲染,那么 css 加载会阻塞 DOM 树的解析和渲染吗?接下来,我就来对 css 加载对 DOM 树的解析和渲染的影响做一个测试。

为了完成本次测试,先来科普一下,如何利用 chrome 来设置下载速度

\1. 打开 chrome 控制台 (按下 F12),可以看到下图,重点在我画红圈的地方

img

点击我画红圈的地方 (No throttling),会看到下图,我们选择 GPRS 这个选项

\2. 点击我画红圈的地方 (No throttling),会看到下图,我们选择 GPRS 这个选项

img

这样,我们对资源的下载速度上限就会被限制成 20kb/s,好,那接下来就进入我们的正题

\3. 这样,我们对资源的下载速度上限就会被限制成 20kb/s,好,那接下来就进入我们的正题

css 加载会阻塞 DOM 树的解析渲染吗?

用代码说话:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>css阻塞</title>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <style>
      h1 {
        color: red !important
      }
    </style>
    <script>
      function h () {
        console.log(document.querySelectorAll('h1'))
      }
      setTimeout(h, 0)
    </script>
    <link href="https://cdn.bootcss.com/bootstrap/4.0.0-alpha.6/css/bootstrap.css" rel="stylesheet">
  </head>
  <body>
    <h1>这是红色的</h1>
  </body>
</html>

假设: css 加载会阻塞 DOM 树解析和渲染

假设结果: 在 bootstrap.css 还没加载完之前,下面的内容不会被解析渲染,那么我们一开始看到的应该是白屏,h1 不会显示出来。并且此时 console.log 的结果应该是一个空数组。

实际结果:如下图

img

css 会阻塞 DOM 树解析?

由上图我们可以看到,当 css 还没加载完成的时候,h1 并没有显示,但是此时控制台输出如下

img

可以得知,此时 DOM 树至少已经解析完成到了 h1 那里,而此时 css 还没加载完成,也就说明,css 并不会阻塞 DOM 树的解析。

css 加载会阻塞 DOM 树渲染?

由上图,我们也可以看到,当 css 还没加载出来的时候,页面显示白屏,直到 css 加载完成之后,红色字体才显示出来,也就是说,下面的内容虽然解析了,但是并没有被渲染出来。所以,css 加载会阻塞 DOM 树渲染。

个人对这种机制的评价

其实我觉得,这可能也是浏览器的一种优化机制。因为你加载 css 的时候,可能会修改下面 DOM 节点的样式,如果 css 加载不阻塞 DOM 树渲染的话,那么当 css 加载完之后,DOM 树可能又得重新重绘或者回流了,这就造成了一些没有必要的损耗。所以我干脆就先把 DOM 树的结构先解析完,把可以做的工作做完,然后等你 css 加载完之后,在根据最终的样式来渲染 DOM 树,这种做法性能方面确实会比较好一点。

css 加载会阻塞 js 运行吗?

由上面的推论,我们可以得出,css 加载不会阻塞 DOM 树解析,但是会阻塞 DOM 树渲染。那么,css 加载会不会阻塞 js 执行呢?

同样,通过代码来验证.

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>css阻塞</title>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <script>
      console.log('before css')
      var startDate = new Date()
    </script>
    <link href="https://cdn.bootcss.com/bootstrap/4.0.0-alpha.6/css/bootstrap.css" rel="stylesheet">
  </head>
  <body>
    <h1>这是红色的</h1>
    <script>
      var endDate = new Date()
      console.log('after css')
      console.log('经过了' + (endDate -startDate) + 'ms')
    </script>
  </body>
</html>

假设: css 加载会阻塞后面的 js 运行

预期结果: 在 link 后面的 js 代码,应该要在 css 加载完成后才会运行

实际结果:

img

由上图我们可以看出,位于 css 加载语句前的那个 js 代码先执行了,但是位于 css 加载语句后面的代码迟迟没有执行,直到 css 加载完成后,它才执行。这也就说明了,css 加载会阻塞后面的 js 语句的执行。详细结果看下图 (css 加载用了 5600+ms):

img

结论

由上所述,我们可以得出以下结论:

  1. css 加载不会阻塞 DOM 树的解析
  2. css 加载会阻塞 DOM 树的渲染
  3. css 加载会阻塞后面 js 语句的执行、

因此,为了避免让用户看到长时间的白屏时间,我们应该尽可能的提高 css 加载速度,比如可以使用以下几种方法:

  1. 使用 CDN(因为 CDN 会根据你的网络状况,替你挑选最近的一个具有缓存内容的节点为你提供资源,因此可以减少加载时间)
  2. 对 css 进行压缩 (可以用很多打包工具,比如 webpack,gulp 等,也可以通过开启 gzip 压缩)
  3. 合理的使用缓存 (设置 cache-control,expires,以及 E-tag 都是不错的,不过要注意一个问题,就是文件更新后,你要避免缓存而带来的影响。其中一个解决防范是在文件名字后面加一个版本号)
  4. 减少 http 请求数,将多个 css 文件合并,或者是干脆直接写成内联样式 (内联样式的一个缺点就是不能缓存)

原理解析

那么为什么会出现上面的现象呢?我们从浏览器的渲染过程来解析下。

不用浏览器使用的内核不同,所以他们的渲染过程也是不一样的。目前主要有两个:

webkit 渲染过程

img

Gecko 渲染过程

img

从上面两个流程图我们可以看出来,浏览器渲染的流程如下:

  1. HTML 解析文件,生成 DOM Tree,解析 CSS 文件生成 CSSOM Tree
  2. 将 Dom Tree 和 CSSOM Tree 结合,生成 Render Tree(渲染树)
  3. 根据 Render Tree 渲染绘制,将像素渲染到屏幕上。

从流程我们可以看出来

  1. DOM 解析和 CSS 解析是两个并行的进程,所以这也解释了为什么 CSS 加载不会阻塞 DOM 的解析。
  2. 然而,由于 Render Tree 是依赖于 DOM Tree 和 CSSOM Tree 的,所以他必须等待到 CSSOM Tree 构建完成,也就是 CSS 资源加载完成 (或者 CSS 资源加载失败) 后,才能开始渲染。因此,CSS 加载是会阻塞 Dom 的渲染的。
  3. 由于 js 可能会操作之前的 Dom 节点和 css 样式,因此浏览器会维持 html 中 css 和 js 的顺序。因此,样式表会在后面的 js 执行前先加载执行完毕。所以 css 会阻塞后面 js 的执行。

DOMContentLoaded

对于浏览器来说,页面加载主要有两个事件,一个是 DOMContentLoaded,另一个是 onLoad。而 onLoad 没什么好说的,就是等待页面的所有资源都加载完成才会触发,这些资源包括 css、js、图片视频等。

而 DOMContentLoaded,顾名思义,就是当页面的内容解析完成后,则触发该事件。那么,正如我们上面讨论过的,css 会阻塞 Dom 渲染和 js 执行,而 js 会阻塞 Dom 解析。那么我们可以做出这样的假设

  1. 当页面只存在 css,或者 js 都在 css 前面,那么 DomContentLoaded 不需要等到 css 加载完毕。
  2. 当页面里同时存在 css 和 js,并且 js 在 css 后面的时候,DomContentLoaded 必须等到 css 和 js 都加载完毕才触发。

我们先对第一种情况做测试:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>css阻塞</title>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <script>
      document.addEventListener('DOMContentLoaded', function() {
        console.log('DOMContentLoaded');
      })
    </script>
    <link href="https://cdn.bootcss.com/bootstrap/4.0.0-alpha.6/css/bootstrap.css" rel="stylesheet">
  </head>
  <body>
  </body>
</html>

实验结果如下图:

img

从动图我们可以看出来,css 还未加载完,就已经触发了 DOMContentLoaded 事件了。因为 css 后面没有任何 js 代码。

接下来我们对第二种情况做测试,很简单,就在 css 后面加一行代码就行了

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>css阻塞</title>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <script>
      document.addEventListener('DOMContentLoaded', function() {
        console.log('DOMContentLoaded');
      })
    </script>
    <link href="https://cdn.bootcss.com/bootstrap/4.0.0-alpha.6/css/bootstrap.css" rel="stylesheet">

    <script>
      console.log('到我了没');
    </script>
  </head>
  <body>
  </body>
</html>

实验结果如下图:

img

我们可以看到,只有在 css 加载完成后,才会触发 DOMContentLoaded 事件。因此,我们可以得出结论:

  1. 如果页面中同时存在 css 和 js,并且存在 js 在 css 后面,则 DOMContentLoaded 事件会在 css 加载完后才执行。
  2. 其他情况下,DOMContentLoaded 都不会等待 css 加载,并且 DOMContentLoaded 事件也不会等待图片、视频等其他资源加载。

以上,就是所有内容。欢迎关注我们的专栏,接收最新最有趣的前端内容。

此文已由腾讯云 + 社区在各渠道发布

获取更多新鲜技术干货,可以关注我们腾讯云技术社区 - 云加社区官方号及知乎机构号

共收到 7 条回复 时间 点赞

现在 js 不都是放在底部的么。

恒温 回复

现在流行 template、js、css 分离,通过 import 或者 require 引入的居多
当然像我这种没追求的会合在一起(当然通用函数、模块也会剥离),like this:

槽神 回复

vue 是这种玩法么?好久没碰前端了

恒温 回复

三大主流框架都差不多的玩法,主要是为了配合 webpack 或者 gulp 等其他的,方便管理打包、压缩、静态化什么的,为了提升编译速度、降低加载的网络开销,局域网应用可能要求稍微松一点,毕竟带宽足够

@ 恒温 @ 槽神
我看不懂恆溫跟槽神的對話....編譯前的代碼跟編譯後不同,所以槽神貼的源碼好像沒意義。儘管實際上沒有試過,但編譯前怎麼寫應該都沒差別,像槽神把 style 放在最下面,我則是習慣接在 template 的後面(個人崇尚 CSS 主導樣式),我沒有很仔細翻過 vue 的文檔,但對於樣式好像沒有特別提到該放在哪邊。

JS 是不是該放在底部,我想編譯過後恐怕還是會被拉到底部去。畢竟很久沒碰 webpack 的文檔,所以編譯過後會是甚麼樣子,我並不清楚。

槽神 回复

我知道它編譯後會全體分開 (當然前提要看有沒有寫關聯設定),我是想說對於恆溫那個 JS 不是都放在底部的問題,光看源碼的.vue 檔恐怕沒有用,應該要把編譯後的.html 也貼出來,這樣就能看出來 webpack 會主動把 JS 的位置放到最後面。

至於編譯速度,我個人是相信機器能力,畢竟應該不會有人刻意在低端機器上進行開發,這麼做沒意義。(在低端測試會比較有意義)

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册