在单页应用程序中有多个文件的情况下,Javascript 异步延迟执行顺序

nik*_*ohn 2 html javascript performance asynchronous deferred

我正在尝试提高我的页面的页面加载性能,这是在 EmberJS 上实现的。

我正在考虑在我们的 Javascript 文件上使用asycdefer。所有其他优化都已经完成(将脚本移动到页面底部,添加asyncdefer分析标签等)。

现在,根据ember-cli规范,生成的index.html有两个脚本标签——一个供应商 JS 文件和一个应用程序 JS 文件。

如果我要实现asyncand defer,我需要确保我的应用程序 JS 文件之前加载我的供应商JS 文件,以确保后者具有初始化应用程序所需的所有代码。

我知道当使用asyncanddefer定义时,获取和解析脚本的顺序是不同的,如定义here

在此处输入图片说明

我的问题是这样的:

如果在同一个页面中有多个 JS 文件,有没有办法按照规定的顺序获取和执行它们?我正在寻找异步请求中的回调/承诺之类的东西,但就实际script标签本身而言。

Ber*_*ech 5

自从这个问题首次发布以来,情况可能已经好转,但似乎在 2019 年您可以推迟您的脚本,并按照脚本标签写入您的 html 文档的顺序处理它们。添加defer到您的vendor脚本和您的main脚本将导致它们并行加载,而不是阻止解析 html 文档,并在文档解析完成时按顺序进行处理。

whatwg脚本文档的4.12.1.1 处理模型部分详细介绍了很多细节,我将在此处进行总结:

  • 如果脚本的类型是“经典”(不是type="module"),并且元素有一个src属性,并且该元素有一个defer属性,并且该元素已被标记为“解析器插入”,并且该元素没有async属性

  • 然后将元素添加到脚本列表的末尾,这些脚本将在准备脚本算法开始时尽快与脚本元素的节点文档相关联地执行

查看链接以获取完整的详细信息,但基本上它似乎是在说延迟脚本将按照它们在 html 文档中解析的顺序进行处理。

MDN同意:

具有该defer属性的脚本将按照它们在文档中出现的顺序执行。

另一个需要注意的要点(来自同一个 MDN 文档):

具有该defer属性的脚本将阻止DOMContentLoaded事件触发,直到脚本加载并完成评估。

还值得注意的是,whatwg 和 MDN 都没有说明将脚本标签放在 html 文档的头部或底部。如果你的所有脚本都具有该defer属性,则在 html 文档解析完成后,它们将按照出现顺序进行处理。将脚本标签放在标题中意味着它们将在 html 文档解析过程的早期开始下载,而不是稍后将它们放置在正文底部的情况。当然,这也取决于您从同一主机并行下载了多少其他资源。

现在有点乱,但总而言之,为了获得最佳的非阻塞性能:

  • 尽可能早地将所有脚本标签放在 html 文档中
  • 按照您希望处理它们的顺序添加它们
  • defer属性添加到所有这些(如果它们不需要同步处理或下载后立即处理)
  • 对于下载后需要立即处理的脚本,添加该async属性。HTML 解析将在脚本下载时继续进行 - 将在脚本下载完成和脚本执行时暂停 - 并在脚本完成执行后恢复。
  • 对于下载后需要立即处理的脚本,以及有修改DOM等副作用的脚本,不要添加asyncdefer。HTML 解析将在脚本下载时暂停 - 在脚本完成下载和脚本执行时将保持暂停 - 并在脚本完成执行后恢复。

2020 年 7 月更新:

在 Chrome 中,同步脚本(没有async或 的那些defer)的下载和解析有了很大改进。下载和解析在单独的线程上完成 - 下载线程在下载时将文件流式传输到解析器中。

结合<link rel="preload">在您<head>的 中,您的文件可能会在 HTML 解析器到达您的<script>标记时被下载- 这意味着它不需要暂停并且可以立即执行脚本:

更好地下载和解析同步脚本

上图取自视频Day 2: Chrome web.dev Live 2020 - What's New in V8 / Javascript - 他们解释下载和解析更新的部分大约有 4 分钟长,但非常值得一看。


Dim*_*nis 1

我可以想到两种方法。

a) 照你说的去做。即有一个 script 标签,里面有两个链接的 Promise,每个脚本标签创建一个新的 script 标签,将其附加到 DOM,添加一个onload事件函数(该函数将是 Promise 的resolve函数),最后将其src属性设置为资源的 URL。当第一个 Promise 的脚本加载时,第二个 Promise 应该执行并做同样的事情。

b) 走中间道路。将供应商文件放在头部,以便同步加载,并将应用程序文件放在文档的最底部,以便在其他一切完成后加载。

在我看来,第一个选择是多余的。

编辑:a) 的示例

<script>
var p = new Promise(function(resolve, reject) {
    var scriptTag = document.createElement('script');
    document.head.appendChild(scriptTag);
    scriptTag.onload = resolve;
    scriptTag.src = 'URL_to_vendor_file';
});

p.then(function() {
  var scriptTag = document.createElement('script');
    document.head.appendChild(scriptTag);
    scriptTag.src = 'URL_to_application_file';
};
</script>
Run Code Online (Sandbox Code Playgroud)

注意:上面的示例可以在不使用 Promise 的情况下编写