Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CSS加载 /JS执行会阻塞页面加载吗? #27

Open
liangbus opened this issue Mar 26, 2020 · 0 comments
Open

CSS加载 /JS执行会阻塞页面加载吗? #27

liangbus opened this issue Mar 26, 2020 · 0 comments

Comments

@liangbus
Copy link
Owner

liangbus commented Mar 26, 2020

之前我有写过一篇 script 标签 async 和 defer 属性,简单介绍过其加载方式对页面加载的影响,这次再聊聊加载样式和 script 标签执行对页面加载的影响

Script

先来看看代码

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<script>
  function sleep(t) {
    const ts = Date.now()
    while (true) {
      const now = Date.now()
      if(now - ts > t) break
    }
  }
  // 锁3秒
  sleep(3000)
  const tag = document.querySelectorAll('h1');
  console.log('tag -> ', tag)
</script>
<body>
  <h1>LOLOLO</h1>
</body>
</html>

这里,我通过一个循环强行占用了 3 秒的 JS 线程,延迟去获取 dom 节点,假如 JS 执行不会阻塞 DOM 的解析,那么 log 出来的数组应该不为空
结果为

tag -> NodeList []

所以,事实就是 JS 的执行会阻塞 DOM 的解析,事实理应如此,假如 DOM 和 JS 执行是并行的,但是浏览器是不知道 script 里面未来将要执行的内容会不会改变我解析出来的 DOM,如果会(比如 document.write 这种操作),那浏览器就相当于白忙活了,所以还不如不解析先,等你 JS 搞完,我的 DOM 再进行解析,所以这里就有了一个优化点,为避免浏览器白屏时间过长,应该尽可能地把脚本放在后面再去执行,避免阻塞 DOM 的解析工作。

CSS

说完内联脚本的执行问题,再来看下样式加载又有什么影响,先来看看代码(省略了部分代码,只展示相关部分)

<script>
  setTimeout(() => {
    const h1Tags = document.querySelectorAll('h1');
    console.log('before element -> ', h1Tags)
  }, 0)
</script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.css">
<body>
  <h1>LOLOLO</h1>
</body>

这里我增加了一个外联的样式,并且在该样式加载写了个 setTimeout 去读取后面的 h1 标签,同时我将浏览器网络设置为比较慢的模式(network -> online 选项卡),此时输出

before element -> NodeList [h1]

仔细观察可以发现,这行 log 会比页面上 "LOLOLO" 几个字更快打印出来,这是因为我们的外部样式请求会比较慢,我测试的时候时候是2s多,所以这几个字是2秒后才渲染在页面上的,而实际上 DOMContentLoaded 事件完成,才16ms,蓝色线,也远在请求完成之前,如下图

image

MDN 解析 DOMContentLoaded Event

The DOMContentLoaded event fires when the initial HTML document has been completely loaded and parsed, without waiting for stylesheets, images, and subframes to finish loading.

所以,DOM 早已经解析好了,并没有等待外部样式加载完成,所以前面的脚本能够通过 setTimeout 来获取到 h1 节点,因此外部样式的加载并不会阻塞 DOM 树的解析,但是会阻塞 DOM 树的渲染(猜想应该是要等待 CSSOM 的完成,然后再合成渲染树,避免样式突然的变化而出现闪烁)

再上代码

<script>
  const before = Date.now()
</script>
<script>
  setTimeout(() => {
    const h1Tags = document.querySelectorAll('h1');
    console.log('before element -> ', h1Tags)
  }, 0)
</script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.css">
<script>
  const after = Date.now()
  console.log('time offset -> ', after - before);
  const tag = document.querySelectorAll('h1');
  console.log('tag -> ', tag)
</script>
<body>
  <h1>LOLOLO</h1>
</body>

这里我把 script 代码放在了外部样式 link 后面,同时加了个时间比对,输出结果为

before element -> NodeList []
time offset -> 2482
tag -> NodeList []

前面的 setTimeout 也拿不到结果了,并且,time offset 是 2s 多,正是加载外部样式的时间!,由此
可以看出,加载样式是会阻塞后续的 script 执行,然后 script 又会导致后面的 dom 无法解析,都在等待该样式表的加载,再看看 DomContentLoaded 事件的时间,

image

可以看出,DOM 树解析的时间把加载样式的时间也算进去了,这也就是我们平常网上经常看到, script 不要紧跟在 link 后面,link 尽量在头部完成,而 script 尽量往后执行,如果必要,则把 script 放在 link 之前执行,这也是一条很重要的优化点。

@liangbus liangbus changed the title CSS/JS 加载会阻塞页面加载吗? CSS加载 /JS执行会阻塞页面加载吗? Mar 26, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant