【问题标题】:Is JavaScript execution deferred until CSSOM is built or not?JavaScript 执行是否延迟到 CSSOM 构建完成?
【发布时间】:2017-11-06 14:36:18
【问题描述】:

自从我阅读/了解 CSSOM 到今天为止,这个问题的答案对我来说一直很清楚。我似乎无法找到最初的文章,但它通过示例非常清楚地解释了 JavaScript 执行被推迟到 CSSOM 由 <head> 中的所有 <style><link> 标记构建(除了那些没有申请,基于@media 查询)。
或者至少我当时是这么认为的,直到今天我都没有理由怀疑它。

这似乎得到了来自 Google 的 Web Fundamentals / Performance 的 this sub-chapter 中粗体声明的支持:

...浏览器延迟脚本执行和 DOM 构建,直到它完成下载和构建 CSSOM。

但是,这一声明受到了我提供的this answer 下的另一位 SO 用户的友好聊天的严重挑战,他提出了以下证明相反的建议:

<head>
  <script>document.write("<!--");</script>
  <style> body { background-color: red; } </style>
  -->
</head>

好的,让我们确定一下。让我们用

替换 &lt;style&gt;
<link rel="stylesheet" type="text/css" href="test.php" />

...并让test.php 挂起几秒钟:

<?php
sleep(10);
header('Content-Type: text/css');
?>

/* adding styles here would be futile */

如果我是对的(并且js 的执行被推迟到 CSSOM 构建完成),在构建 CSSOM 之前和执行 &lt;script&gt; 之前,页面会保持空白 10 秒,这会将 &lt;link /&gt; 注释掉并允许要呈现的页面。

如果他是对的,js 会在遇到时运行,&lt;link /&gt; 请求永远不会离开,因为它现在是评论。

惊喜:

  • 页面立即呈现。 他是对的!
  • &lt;link /&gt; 请求离开并且浏览器选项卡显示加载图标10 秒。 我也是对的!还是我?我很困惑,这就是我......

有人能对此有所了解吗?这是怎么回事?
document.write有关系吗?
是否与加载.php 文件而不是.css 有关?


如果有什么不同,我在 Ubuntu 上的 Chrome 中进行了测试。

我恳请您链接一个可靠的(重新)资源或提供一个雄辩的示例/测试来支持您可能考虑提供的任何答案。

【问题讨论】:

  • 我觉得没有资格回答这个问题。我确实找到了这篇文章developers.google.com/web/fundamentals/performance/… 并在该部分中指出:“即使脚本直接内联到页面中,浏览器也无法执行它,直到构建 CSSOM。简而言之,内联 JavaScript 也是解析器阻塞。”
  • @gforce301 我知道。您会在该文档的多个位置找到此声明(否则它非常可靠且非常好读),但它与浏览器中发生的事情相冲突,我认为。在他们的浏览器中,甚至。有些东西没有加起来。
  • 出于好奇,如果您在 document.write 之前在脚本中放置对 CSSOM 的依赖,它会改变结果吗?例如,console.log(document.documentElement.style.width);
  • @TravisJ console.log(document.documentElement.style.width); 不输出任何内容(空行)。但是,console.log('nothing') 会输出您所期望的:nothing。它会立即发生。
  • 是的,我知道它什么都不输出(还没有计算样式),但是我只是好奇访问该样式是否会导致对 CSSOM 的依赖。

标签: javascript css performance cssom


【解决方案1】:

这句话是对的,但它意味着如果您先放置样式表,然后再放置一个脚本,则该脚本将在样式表下载并解析之前不会执行。看这个例子:

test.php:

<?php
sleep(5);
header('Content-Type: text/css');
echo 'body {background-color: red;}';

index.html:

<link rel="stylesheet" href="test.php">
<script>console.log('done');</script>

在背景颜色变为红色之前,不会执行console.log 调用。

由此得出的结论是,构建 CSSOM 并不是对所有样式表都一次性完成,而是一个渐进的过程——当浏览器遇到样式表时,它会下载、解析并移动下一步。也可能浏览器首先列出所有 CSS 资源并将它们添加到下载队列中,甚至在执行任何脚本之前。这可以解释为什么即使 link 标记已被脚本注释,也会发出请求。

【讨论】:

  • 这基本上使我们得出结论,&lt;head&gt; 解析实际上是单线程的。并且 CSSOM 的构建是渐进的,分阶段完成,因为每个 &lt;style&gt;&lt;link /&gt; 标记都被满足和解析。这不是从 &lt;head&gt; 读取所有 CSS 资源的过程,而是将 &lt;head&gt; 中的所有 &lt;script&gt;s 推迟到最后的 "head" CSSOM 完成,之后所有加载的 &lt;script&gt;s 将执行,正如我之前所想的那样,在文档渲染开始之前。但它仍然没有解释为什么在我的示例中请求test.php。大概是在js运行之前制作了一个head资源的列表。
  • @AndreiGheorghiu 在您的示例中,发出的请求看起来像一个浏览器错误,因为如果您在控制台中检查 DOM 树,您会看到 link 标记已被注释。
  • 我会说浏览器首先读取所有 CSS 资源的列表并在单独的进程中加载​​它,因为它是一个必须加载的列表(在 99.99% 的情况下 - 以上是例外)。在单独的线程中执行此操作会使进程更快,因为它不容易被主线程阻塞。我会说这是一个功能,而不是一个错误。感谢您花时间回答。
  • @AndreiGheorghiu 但至少它应该在标签被评论时中止请求。
  • @MichałPerłakowski 我做了一些测试,它确实“看起来”是一个浏览器错误。 Firefox 和 Chrome 都调用以加载将在 开始执行 html 注释的内联脚本之前被注释掉的资​​源。然后它们都加载文件,即使它们从不解析或执行它们。您可以在其开发人员工具的网络选项卡中看到这种情况。奇怪的是,IE 11 并没有这样做(恕我直言,这是唯一正确的行为)。它完全忽略了“将被注释掉”的标签。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-02-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多