如果你没有动态加载脚本或将它们标记为
defer
或
async
,那么脚本将按照在页面中遇到的顺序加载。无论是外部脚本还是内联脚本 - 它们都按照在页面中遇到的顺序执行。在外部脚本之后出现的内联脚本会等待所有在它之前的外部脚本加载和运行完毕。
异步脚本(不管它们如何被指定为异步)以不可预测的顺序加载和运行。浏览器并行加载它们,并可以自由地按任意顺序运行它们。
多个异步事物之间没有可预测的顺序。如果需要有可预测的顺序,则必须通过注册异步脚本的加载通知并手动排序javascript调用来实现。
当动态插入脚本标签时,执行顺序的行为取决于浏览器。您可以在
this reference article中查看Firefox的行为。简而言之,Firefox的较新版本将动态添加的脚本标签默认设置为异步,除非已经设置了脚本标签。
使用async属性的脚本标签一旦加载就可以运行。实际上,浏览器可能会暂停解析器正在进行的任何其他操作并运行该脚本。因此,它真的可以在几乎任何时间运行。如果脚本被缓存,它可能会立即运行。如果脚本需要较长时间才能加载,则可能在解析器完成后运行。唯一需要记住的是,async可以随时运行,而且时间是不可预测的。
具有defer属性的脚本标记将等待整个解析器完成,然后按照遇到的顺序运行所有标记为defer的脚本。这使您可以将几个相互依赖的脚本标记为defer。它们都将被推迟到文档解析器完成之后运行,但它们将按照它们遇到的顺序执行,保留它们的依赖关系。我认为defer就像将脚本放入队列中,在解析器完成后将进行处理。从技术上讲,浏览器可能会随时在后台下载脚本,但在解析器完成解析页面并解析和运行未标记为defer或async的任何内联脚本之后,它们不会执行或阻塞解析器。
以下是该文章的一句引用:
在IE和WebKit中,插入脚本是异步执行的,但在Opera和4.0之前的Firefox中是同步执行的。
对于较新的兼容浏览器,HTML5规范的相关部分在这里。其中有很多关于异步行为的描述。显然,这个规范不适用于旧浏览器(或非标准浏览器),你可能需要测试它们的行为才能确定。
来自HTML5规范的一句引用:
然后,必须遵循以下第一个描述情况的选项:
如果元素具有src属性,并且元素具有defer属性,并且元素已标记为“parser-inserted”,并且元素没有async属性,则该元素必须添加到将在解析文档完成时执行与创建元素的解析器相关联的脚本列表的末尾。
网络任务源完成提取算法后放置在任务队列上的任务必须设置元素的“准备好执行解析器”标志。解析器将处理执行脚本。
如果元素具有src属性,并且元素已标记为“parser-inserted”,并且元素没有async属性,则该元素是创建元素的解析器的文档的待处理解析阻止脚本。(每个文档一次只能有一个这样的脚本。)
网络任务源完成提取算法后放置在任务队列上的任务必须设置元素的“准备好执行解析器”标志。解析器将处理执行脚本。
如果元素没有src属性,并且元素已标记为“parser-inserted”,并且HTML解析器或XML解析器的文档具有阻止脚本的样式表,则该元素是创建元素的解析器的文档的待处理解析阻止脚本。(每个文档一次只能有一个这样的脚本。)
设置元素的“准备好执行解析器”标志。解析器将处理执行脚本。
如果元素具有src属性,没有async属性,并且没有设置“force-async”标志,则该元素必须添加到将按顺序尽快执行与脚本元素在准备脚本算法开始时相关联的文档的脚本列表的末尾。
网络任务源完成提取算法后放置在任务队列上的任务必须运行以下步骤:
如果元素现在不是添加到上面的将按顺序尽快执行的脚本列表中的第一个元素,则将该元素标记为准备好,但中止执行脚本的这些步骤。
执行:执行此列表中将尽快按顺序执行的第一个脚本元素对应的脚本块。
从将按顺序尽快执行的脚本列表中删除第一个元素。
如果将按顺序尽快执行的脚本列表仍然不为空并且已经将第一个条目标记为就绪,则跳回标记为执行的步骤。
如果元素具有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 Module Loading。