【问题标题】:Are deferred scripts executed before DOMContentLoaded event?延迟脚本是否在 DOMContentLoaded 事件之前执行?
【发布时间】:2017-08-14 12:05:35
【问题描述】:

根据延迟属性MDN says:

此布尔属性设置为向浏览器指示脚本应在文档被解析之后执行,但在触发 DOMContentLoaded 之前。 defer 属性只能用于外部脚本。

开启DOMContentLoadedMDN also says

当初始 HTML 文档完全加载和解析后触发 DOMContentLoaded 事件,无需等待样式表...

所以DOMContentLoadedCSSOM 准备好之前被触发。这意味着延迟脚本在CSSOM 准备好之前执行。但如果这是真的,则脚本必须无法获得正确的 CSS 属性值,并且不能正确应用 CSS。但这不是真的,我们知道所有的延迟脚本都能正常工作。

  1. 那么 MDN 文档在技术上不正确吗?
  2. 在哪里可以找到 DOMContentLoaded` 的官方文档?我在https://dom.spec.whatwg.org/ 中搜索过,但找不到。

P.S: 请注意google says CSSOM 是在执行任何内联 javascript 之前构建的

但 Google 在技术上是不正确的。内联 JavaScript 在 CSSOM 准备好之前执行。从我的测试中,我发现 MDN 是正确的,如果 js 文件(延迟和非延迟)在 CSS 文件(或 js 是内联的)之前下载,那么 js 在 CSSOM 准备好之前执行。所以 js 可能会错误地处理样式。为了避免这种情况,我们需要在所有 js 逻辑之前进行强制回流。

因此,如果用户访问我们的网站时所有需要的 js 都已缓存并且 CSS 未缓存或 js 在 CSS 之前下载,那么他可能会看到一个错误呈现的页面。为了避免这种情况,我们应该在我们所有网站的 js 文件中添加强制回流。

【问题讨论】:

标签: javascript html css dom cssom


【解决方案1】:

我使用延迟脚本加载。某位著名的网站性能专家给出了冗长的技术解释。他明确指出,延迟是要走的路(出于这个和那个技术原因,有各种数据和图表支持,许多人似乎觉得可以广泛讨论,re: async)。

所以我开始使用它。延迟脚本具有下载异步的优势,但按呈现的顺序执行,这可能是异步的问题(例如,您可以在供应商包之前加载应用程序包,因为您不能通过说来控制异步脚本的执行顺序“按此顺序”)。

但是,我马上发现,虽然这解决了这个问题,但这可能意味着,根据您获取包的方式,CSS 包没有加载。因此,您最终可能会得到无样式的内容,具体取决于您的设置方式。请注意,对于延迟,他们还说您不应该在这些脚本中写入 dom 等(这在您的文档方面再次有意义)。

看来您的文档是正确的。效果很容易重现。

我该如何摆脱它;最基本的方式,是这样的:

<script src="css.bundle.js"></script>
<script src="vendor.bundle.js" defer></script>
<script src="angular.bundle.js" defer></script>
<script src="app.bundle.js" defer></script>

这确保 css 首先加载,因此您的主页等会很好地显示,并且还确保(尽管所有三个都加载异步)app.bundle 将最后执行,确保所有其他依赖项有秩序。

因此,您使用启动应用程序所需的绝对最低限度的 CSS,将其创建为一个包,然后首先加载它,然后再进行任何操作。否则,您可以在每个模块/组件中捆绑您的 CSS,依此类推。

这个话题还有很多,我可能会做更多,但是再次(我会尝试找到参考),这是那个性能向导公开推荐的,所以我尝试了它,它对我来说似乎很有效.

编辑:令人着迷的是,在寻找该参考资料(我还没有找到)时,我通过了一些关于该主题的“专家”。这些建议大相径庭。有人说异步在所有方面都优越得多,有人说延迟。陪审团似乎真的对这个话题不感兴趣,总的来说,我想说它可能更多地与你如何构建你的脚本有关,而不是一个实际上是否比另一个更好。

再次编辑:这里有更多证据。我使用上述简单的加载序列在存根网站上运行了性能分析器,故意使脚本变得幼稚,以便它们在时间轴中可见。

这是结果的 SS:这里有四个黄色框。前三个是脚本的评估。第四个(当您在工具中将鼠标悬停在它上面时,这只是 SS 记住的)是 DOMContentLoaded 事件(带有红色角的那个)。

【讨论】:

  • css.bundle.js 是做什么的?省略defer 是否会强制浏览器首先下载css resources 然后css.bundle.js(直到现在CSSOM 准备就绪)并且所有延迟脚本都应该在css.bundle.js 之后下载?
  • CSS Bundle 是 CSS 文件的捆绑版本(我使用 webpack)。这个想法是将所有这些脚本/css标签从您的索引页面中取出并智能地捆绑它们,以便您可以精确控制它们的加载方式和时间。在此示例中,您假设 css.bundle 具有用于在加载其他脚本时设置主页样式的样式。没有 defer 或 async 标签的脚本将按照您放置它们的顺序下载和评估。所以是的,这里的 css 包将首先加载,然后其他所有内容都将延迟加载(因此是异步的),但这些脚本将按指定的顺序 eval(执行)。
  • 如果我理解正确的话。 defer on css.bundle 不会改变执行顺序。我没用过webpack。但我猜css.bundle 应该在头部添加style 标签并在其中附加样式。我的问题是,如果这是 css.bundle 所做的,那么假设 vendor.bundle.jscss.bundle.js 之前下载,vendor.bundle 会在 CSSOM 准备好之前执行吗?
  • 如果供应商包中的组件需要样式表数据,而您的样式表数据都在该包中但尚未执行,您将看到无样式的内容(或者更糟,取决于有多少逻辑在你的CSS)。因此,您要确保不会发生这种情况。我看到人们将他们的主页样式内联来处理这个问题(你把其他所有东西都放在 css 包中)。
  • 将所有样式内联(这并不总是可能的,例如我正在使用引导 cdn)只有当我至少有一个非内联 js 文件并且也没有缓存在用户上时才能解决问题代理人。否则,我将强制回流作为完美的解决方案。
【解决方案2】:

不过,我并没有真正阅读规范。以下内容基于 Chrome 的实际行为(在 Chromium 68、Ubuntu 上观察)。如果它们只是在规范中未定义,则浏览器的行为可能会有所不同。例如在 2010 年scripts don't always wait for proceeding stylesheets。我假设协议已经达成并且这些年来行为已经标准化。


defer 脚本在domInteractive 之后,domContentLoaded 之前执行;它是连续的。

domInteractivedomContentLoaded 是两个时间戳,可以在 Chrome 开发工具的性能(以前的时间线)选项卡中查看。可能也在其他类似的工具中,但我没有尝试过。

domInteractive 是 HTML 解析和初始 DOM 构建完成的时间点(并且所有“同步”脚本都已完成执行)。 document.readyState'loading' 更改为 'interactive'readystatechange 事件相应地在 document 上触发。

所有defer 脚本都按照它们出现的顺序执行。然后是domContentLoadedDOMContentLoaded 事件在document 上触发。

DOM 和 CSSOM 构建不相互依赖;但同步脚本可能会引入依赖关系。

每个同步脚本,无论是内部的还是外部的,都等待前面的样式表被解析(当然,在获取之后)。

是的,后续样式表不会阻止同步脚本。 MDN 和 Google 等文章说“脚本依赖 CSSOM 才能准备好”;他们(可能)没有提到只有前面的部分是依赖的。

P.S:请不要说谷歌说 CSSOM 是在执行任何内联 javscript 之前构建的

Google 没有这么说(至少在我阅读本文时)。

相反,在获取(如果是外部的)并执行一个同步脚本之前,它后面的任何代码、HTML、样式表或其他脚本都不能被解析/执行/构造。它们会阻止它们之后的任何东西。

因此,在特定情况下,例如。如果没有同步脚本,DOMContentLoaded 事件可能会在之前或之后触发 CSSOM 准备好。这就是 MDN 所说的“无需等待样式表”的意思。

defer/async 脚本根本不关心样式表。

与同步脚本不同,defer/async 脚本不会等待前面的样式表,也不会阻止后续的样式表/脚本。它们完全从那些“依赖链”中删除。您不能依赖任何正在进行的样式表来进行解析。

defer/async的区别:

  • 如上所述,defer 脚本的执行时间可预测; DOM 已经准备好了。他们还承诺按顺序执行。

    更新: defer 脚本被添加到列表的 end 中,如 W3C's spec (the 20th item)
    (also in WHATWG's spec)

  • async 脚本对执行顺序没有承诺;每个async 脚本一旦被提取就会被“排队执行”;一旦渲染进程空闲,它们就会被执行。 (准确地说,不同类型的资源有不同的优先级。The spec提供了宝贵的需求)

这些应该很好解释hinok's two examples,前者async(来自Google)和后者defer


我没有太多在页面加载时使用 CSSOM 的经验(不过,我确实在页面加载时对 DOM 进行操作),所以我无法提供可靠的建议。似乎“window 上的load 事件”或“提前强制回流”可能有效。

【讨论】:

  • 您说:“所有延迟脚本都按照它们出现的顺序执行。”我不确定在所有浏览器中都是如此。规范有要求吗?
  • @Flimm Answser 已更新。 defer 脚本被添加到列表的末尾,因此它们按顺序执行。
【解决方案3】:

DOMContentLoaded 可以在 CSSOM 之前触发,source

domContentLoaded 事件在 HTML 被解析后不久触发;浏览器知道不阻塞 JavaScript,并且由于没有其他解析器阻塞脚本,CSSOM 构建也可以并行进行。

Google Developer 上的文章描述了 async 而不是 defer,但就您的问题而言,它没有任何改变,因为基于 perfplanet 上的 Steve Sourders article

DEFER 脚本在 DOM Interactive 之后执行。

his comment在他的文章下

[...] 规范说 DEFER 脚本在 domInteractive 之后但在 domContentLoaded 之前运行。

您可以进行自己的实验,使用defer 和时间线查看下面的代码

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>JS Bin</title>
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha.6/css/bootstrap.css">
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/angular-material/1.1.3/angular-material.css">
</head>
<body>
  <h1>App</h1>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha.6/js/bootstrap.js" defer></script>
</body>
</html>

【讨论】:

猜你喜欢
  • 2018-09-29
  • 2017-08-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-07-13
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多