翻译自stackoverflow.com/a/8996894/9…
默认加载
若不动态地加载或是显式地添加“defer”或“async”属性,script标签将按照在页面上的出现顺序进行加载。无论是外部脚本还是内联脚本都将这样进行处理。也就是说在所有外部脚本后面的内联脚本将等待所有外部脚本执行完后才会执行。
Async属性
具有async属性的script标签加载和执行的顺序是不确定的。浏览器将同步加载它们,然后在浏览器各自认为合适的时候以合适的顺序执行。
并且多个具有async属性的script标签之间也没有明确的执行顺序先后关系。如果在特定情况下需要按顺序执行,可以在代码内注册一个关于加载完成的事件,待收到完成的事件后再加载后续的脚本。
async类型的script标签可能会在加载后直接运行。但是,事实上,浏览器很有可能会因为某些原因暂停它的加载或解析。所以什么时候执行async类型的script标签中的脚本是不确定的。如果脚本已经被缓存,可能会被立即执行。但是如果需要一段时间来加载,那么就需要在解析后再进行执行。所以关于具有async属性的script标签只需要记住一点,就是其脚本的执行时机是不确定的。
动态加载
当动态地添加script标签时,执行顺序将因浏览器的实现而异。概括来说,在最新版本的Firefox浏览器中会将动态添加的且为指明script加载类型的script标签默认添加async属性。
Defer属性
具有defer属性的script标签将会在解释器工作全部完成后,按照具有defer属性的script标签顺序进行执行。这使得开发者可以按顺序使用相互间存在依赖关系的脚本。虽然所有的脚本会在所有的文档内容解析完成后执行,但是浏览器会保证它们按照顺执行。猜测可能在解析过程中defer类型的脚本会被暂时丢进一个队列中,以便文档解析结束后按顺序执行。理论上讲,浏览器可能会在任何时候下载脚本,但是在文档解析完成以及不具有asyn和defer属性的行内脚本解析并执行完成前都不会暂停。
module类型的script标签
所有的type属性为module的script标签都会自动的添加defer属性。浏览器将同步地下载脚本,待到文档解析结束后按顺序执行。
但是模块类型的script标签,也是可以显式地添加async属性的,脚本将同步下载并尽快执行,不用等到文档解析完成后,执行顺序不确定。
最后,下面是一张非常有用的图,显示不同组合情况下脚本下载及执行的时序