什么defer
和async
意味着什么呢?
默认情况下,<script src=...></script>
标签是邪恶的!浏览器必须停止解析 HTML,直到下载并执行脚本(因为脚本可能会调用document.write(...)
或定义以后的脚本所依赖的全局变量)。这意味着脚本标签之后的任何图像和样式表在脚本完成下载和执行之前不会开始下载。外部脚本通常会使 Web 加载更慢,这就是 NoScript 变得如此流行的原因。
微软推出defer
来解决这个问题。如果你使用<script defer src=...></script>
,你保证不打电话document.write(...)
。一个defer
外部脚本就会立即开始下载,但不会执行,直到页面之后被渲染。页面呈现后,所有defer
脚本都按照声明的顺序执行。并非所有浏览器都实现defer
了。
HTML5 引入了async
可以在任何时间执行的属性——可能在页面完成解析之前,甚至在其他defer
/async
仍在下载的脚本之前。但是使用多个async
脚本比较困难,因为它们的执行顺序没有保证。就像defer
,并非所有浏览器都实现async
了。
在执行完所有defer
和async
脚本之后,将触发DOMContentLoaded
和load
事件。
简史defer
和async
- 1997 IE 4 引入
defer
.
- 1998 HTML 4 规范提到了
defer
,但不幸的是它没有准确说明defer
脚本执行的时间(全部按顺序?之前onload
?)。因此,没有其他浏览器实施,defer
因为没有人想对 IE 的行为进行逆向工程或破坏可能取决于 IE 特性的脚本。(例如,请参阅Mozilla 功能请求)。
- 2006 HTML5 草案最终描述了实现所需的细节
defer
: defer
所有脚本都应该在解析页面其余部分之后和之前按顺序执行onload
。它还引入async
了指定可以在下载时执行的脚本,而不必相互等待。不幸的是,HTML5 不允许内联defer
脚本与 IE 相矛盾。这打破了所有defer
脚本按顺序执行的不变性(如果某些defer
脚本src
具有内联内容,而某些脚本具有内联内容)。
- 2009 Gecko 1.9.1 (Firefox 3.5)支持
defer
.
- 2010-01 Gecko 1.9.2 (Firefox 3.6)支持
async
.
- 2010-09
defer
并async
签入 Webkit。您应该很快就会在 Chrome 和 Safari 中看到它(它已经在 Chrome 开发者频道中,但它有点问题)。
- 我们仍在等待 Opera 的实现
defer
和async
IE 的实现async
。
那么 Web 开发人员应该使用什么?
目前没有单一的规则可以遵循。您必须为访问您网站的浏览器组选择最能平衡简单性、页面呈现延迟和脚本执行延迟的解决方案。
- 正如其他人指出的那样,在脚本执行之前呈现页面的最简单方法是将脚本放在页面底部。但是,如果脚本是必不可少的,或者网页包含大量 HTML,那么您应该将脚本放在页面的更高位置。
- 如果您的脚本是独立的并且您的客户使用 IE 或新版本的 Firefox,请使用
<script async defer src=...></script>
:这允许呈现与 IE 和最新的 HTML5 浏览器的脚本下载并行继续,但会导致 HTML5 之前的浏览器(包括所有版本的 Opera)被阻止.
- 如果一个外部脚本依赖于另一个,标记它们
defer
(但不是async
),它们将按照它们声明的顺序执行(除了IE<=9 在某些条件下可以乱序执行它们)。同样,这允许在 IE 和支持 HTML5 的 Gecko/Webkit 中的脚本下载的同时继续进行渲染,但较旧的浏览器和 Opera 会受到影响。defer
即使脚本位于页面底部,也最好使用这些脚本,以便它们彼此并行下载。
- 永远不要
defer
用于内联脚本,因为 HTML5 草案已经取消了执行顺序保证。
- 如果您的受众包括许多 Opera 或老 Firefox/Safari 用户,以下代码段将在大多数 HTML5 之前的浏览器(IE、Webkit,需要测试旧版 Firefox)上解析文档后执行脚本,而最新的支持 HTML5 的浏览器启动立即下载但不会因为该
async
属性而阻止执行脚本。换句话说,大多数较旧的浏览器将其视为页面底部的脚本,而最新的浏览器会识别async
. 但是 Opera 用户得到了两全其美的结果,因为 Opera 立即开始执行并且不理解async
. 这是Google Analytics在许多网页上为海胆推荐的模式。
片段:
<script>
(function() {
var script = document.createElement('script');
script.src = '...';
script.async = true;
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(script, s);
})();
</script>
更新:如果您将脚本拆分为module并希望提高性能,我建议您阅读Steve Souder 所著的Even Faster Web Sites的“耦合异步脚本”一章。它包含的提示/技巧不仅可以控制执行顺序,还可以延迟脚本解析以提高性能。