脚本的加载和执行顺序

新手上路,请多包涵

在 html 页面中包含 JavaScript 有很多不同的方法。我知道以下选项:

  • 内联代码或从外部 URI 加载

  • 包含在 或 标签 [ 1 , 2 ]

  • 没有、 deferasync 属性(仅外部脚本)

  • 包含在静态源中或由其他脚本动态添加(在不同的解析状态下,使用不同的方法)

不计算来自硬盘的浏览器脚本、javascript:URIs 和 onEvent -attributes [ 3 ],已经有 16 种替代方法可以让 JS 执行,我确定我忘记了一些东西。

我不太关心快速(并行)加载,我对执行顺序更好奇(这可能取决于加载顺序和 文档顺序)。 是否有 涵盖所有情况的良好(跨浏览器)参考? 例如 http://www.websiteoptimization.com/speed/tweak/defer/ 只处理其中的 6 个,并且主要测试旧浏览器。

我担心没有,这是我的具体问题:我有一些(外部)头脚本用于初始化和脚本加载。然后我在正文的末尾有两个静态的内联脚本。第一个允许脚本加载器动态地将另一个脚本元素(引用外部 js)附加到正文。第二个静态内联脚本想要使用添加的外部脚本中的 js。它可以依赖于另一个已被执行(以及为什么:-)?

原文由 Bergi 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 734
2 个回答

如果您没有动态加载脚本或将它们标记为 deferasync ,则脚本将按照页面中遇到的顺序加载。无论是外部脚本还是内联脚本都没有关系 - 它们按照在页面中遇到的顺序执行。外部脚本之后的内联脚本会一直保留,直到它们之前的所有外部脚本都已加载并运行。

异步脚本(无论它们如何指定为异步)以不可预知的顺序加载和运行。浏览器并行加载它们,并且可以按照它想要的任何顺序自由运行它们。

多个异步事物之间没有可预测的顺序。如果需要一个可预测的顺序,则必须通过注册来自异步脚本的加载通知并在加载适当的内容时手动排序 javascript 调用来对其进行编码。

当动态插入脚本标签时,执行顺序的行为将取决于浏览器。您可以在 这篇参考文章 中了解 Firefox 的行为方式。简而言之,新版本的 Firefox 默认将动态添加的脚本标签设置为异步,除非另外设置了脚本标签。

带有 async 的脚本标签可以在加载后立即运行。实际上,浏览器可能会暂停解析器正在执行的任何其他操作并运行该脚本。因此,它几乎可以随时运行。如果脚本被缓存,它可能几乎立即运行。如果脚本需要一段时间才能加载,它可能会在解析器完成后运行。使用 async 要记住的一件事是它可以随时运行,而且时间是不可预测的。

带有 defer 的脚本标签会等待整个解析器完成,然后按照遇到的顺序运行所有带有 defer 标记的脚本。这允许您将多个相互依赖的脚本标记为 defer 。它们都将被推迟到文档解析器完成之后,但它们将按照遇到的顺序执行,保留它们的依赖关系。我认为 defer 就像脚本被放入将在解析器完成后处理的队列中一样。从技术上讲,浏览器可能随时在后台下载脚本,但在解析器完成页面解析并解析和运行任何未标记为 deferasync 的内联脚本之前,它们不会执行或阻止解析器。

这是那篇文章的引述:

插入脚本的脚本在 IE 和 WebKit 中异步执行,但是

在 Opera 和 4.0 之前的 Firefox 中同步。

HTML5 规范(适用于较新的兼容浏览器)的相关部分在 此处。那里有很多关于异步行为的文章。显然,此规范不适用于您可能需要测试才能确定其行为的旧浏览器(或不符合标准的浏览器)。

引用 HTML5 规范:

然后,描述情况的以下选项中的第一个

必须遵守:

如果元素有 src 属性,并且元素有 defer

属性,并且该元素已被标记为“解析器插入”,并且

该元素没有异步属性

必须添加元素

到文档时将执行的脚本列表的末尾

已完成与解析器的 Document 关联的解析

创建了元素。

联网任务源在任务队列中放置一次的任务

获取算法已经完成必须设置元素的“准备好

被解析器执行”标志。解析器将处理执行脚本。

如果元素具有 src 属性,并且该元素已被标记

作为“解析器插入”,并且该元素没有异步属性

该元素是 Document 的待处理解析阻止脚本

创建元素的解析器。 (只能有这样一种

一次为每个文档编写脚本。)

联网任务源在任务队列中放置一次的任务

获取算法已经完成必须设置元素的“准备好

被解析器执行”标志。解析器将处理执行脚本。

如果元素没有 src 属性,并且该元素已经

标记为“已插入解析器”,以及 HTML 解析器的文档或

创建脚本元素的 XML 解析器有一个样式表,它是

阻塞脚本 元素是待处理的解析阻塞脚本

创建元素的解析器的文档。 (只能有

一次是每个文档一个这样的脚本。)

设置元素的“准备好被解析器执行”标志。解析器将

处理执行脚本。

如果元素有 src 属性,没有 async 属性,

并且没有设置“force-async”标志 必须添加元素

到将尽快按顺序执行的脚本列表的末尾

尽可能与脚本元素的 Document 相关联

准备脚本算法开始的时间。

联网任务源在任务队列中放置一次的任务

获取算法已完成必须运行以下步骤:

如果该元素现在不是脚本列表中的第一个元素

它将尽快按添加到的顺序执行

上面, 然后将元素标记为就绪,但中止这些步骤而不

执行脚本呢。

执行:执行第一个脚本对应的脚本块

此脚本列表中的元素,将立即按顺序执行

可能的。

从将要执行的脚本列表中删除第一个元素

为了尽快。

如果这个脚本列表将尽快按顺序执行

仍然不是空的并且第一个条目已经被标记为

准备好,然后跳回到标有执行的步骤。

如果元素具有 src 属性,则该元素必须添加到

将尽快执行文档的一组脚本

准备脚本算法时的脚本元素

开始了。

联网任务源在任务队列中放置一次的任务

获取算法已完成必须执行脚本块和

然后从将执行为的脚本集中删除该元素

尽快。

否则 用户代理必须立即执行脚本块,

即使其他脚本已经在执行。


Javascript 模块脚本 type="module" 怎么样?

Javascript 现在支持使用如下语法加载模块:

 <script type="module">
 import {addTextToBody} from './utils.mjs';

 addTextToBody('Modules are pretty cool.');
 </script>

或者,使用 src 属性:

 <script type="module" src="http://somedomain.com/somescript.mjs">
 </script>

所有 type="module" 的脚本都会自动赋予 defer 属性。这将与页面的其他加载并行(如果不是内联)下载它们,然后按顺序运行它们,但在解析器完成之后。

模块脚本也可以被赋予 async 属性,它将尽快运行内联模块脚本,而不是等到解析器完成并且不等待以相对于其他脚本的任何特定顺序运行 async 脚本。

有一个非常有用的时间线图,显示了不同脚本组合的获取和执行,包括本文中的模块脚本: Javascript 模块加载

原文由 jfriend00 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
logo
Stack Overflow 翻译
子站问答
访问
宣传栏