<script defer="defer"> 的作用是什么?它是如何工作的?

224

我有几个<script>元素,其中一些代码依赖于其他<script>元素中的代码。我看到defer属性可以派上用场,因为它允许延迟执行代码块。

为了测试它,我在Chrome上执行了这个:http://jsfiddle.net/xXZMN/

<script defer="defer">alert(2);</script>
<script>alert(1)</script>
<script defer="defer">alert(3);</script>

不过,它弹出的是2-1-3。为什么它没有弹出1-2-3


2
也许可以看看这篇文章。而且,像往常一样,IE有自己的理解,决定先加载脚本,但延迟执行直到body加载完成(通常情况下)。 - Brad Christie
谢谢,不过在Chrome上测试页面的结果与预期不同:http://www.websiteoptimization.com/speed/tweak/defer/test/。截图显示了我期望的结果,而Chrome似乎只是先执行了延迟加载的内容。 - pimvdb
1
我认为您会发现IE对defer的定义与W3C在DOM Level 1规范中对defer的意图相匹配。 - Mark At Ramp51
45
正如Alohci在他的回答中指出的那样,根据HTML标准,只有在指定srcdefer才是有效的。这可能是你的示例在大多数浏览器中未能按预期工作的原因。 - Pankrat
3
真实故事!尝试使用 http://jsfiddle.net/xXZMN/50/ 在Firefox24中测试。 - m93a
1
发现这张图很有帮助,可以理解defer和async之间的区别:http://www.growingwiththeweb.com/2014/02/async-vs-defer-attributes.html - neelmeg
11个回答

173

HTML5规范中的几个片段:http://w3c.github.io/html/semantics-scripting.html#element-attrdef-script-async

如果没有src属性,则不得指定defer和async属性。


使用这些属性[async 和 defer]可以选择三种可能的模式。如果存在async属性,则脚本将异步执行,只要它可用即可。如果未出现async属性但存在defer属性,则在页面完成解析时执行该脚本。如果两个属性都不存在,则立即获取并执行脚本,然后用户代理继续解析页面。


由于历史原因,这些属性的确切处理细节有些复杂,涉及HTML的许多方面。因此,实现要求必须分散在规范中。本节中的算法描述了这个处理的核心,但是这些算法参考并被HTML中脚本开始和结束标记的解析规则、foreign content和XML以及document.write()方法的处理等所引用和引用。


如果元素具有src属性,并且元素具有defer属性,并且已将元素标记为“parser-inserted”,并且元素没有async属性:

必须将该元素添加到由创建元素的解析器关联的文档的解析完成时将执行的脚本列表的末尾。


40
也许我的回答可以阻止人们因为你的无用评论而对我的回答进行投票。被接受的答案并没有错,这个回答不同的原因是在2011年初,HTML5规范对于主流的网络浏览器来说比现在不那么重要。这个回答可能更适用于未来,但按任何标准来看,被接受的答案都不是错误的。 - Mark At Ramp51
3
虽然了解规范很有用,但是事实证明一些浏览器(例如IE<9)实现defer的方式不好。如果使用defer,在某些浏览器中不能保证脚本文件按顺序执行。 - Flimm
2
@Flimm 不仅仅是IE,似乎在Firefox中执行顺序也不能保证 - Franklin Yu
第一个引用不再有效了,对吧?现在我可以这样阅读:"如果未提供 src 属性或脚本不是经典脚本,则不能指定该属性。" 经典脚本也是指没有 src = "" 的脚本。 - Félix Sanz

162

真正的答案是:因为你不能信任defer。

从概念上讲,defer和async的区别如下:

async允许脚本在后台下载而不阻塞。然后,在它完成下载时,渲染被阻塞,并且该脚本执行。当脚本执行完毕后,渲染恢复。

defer也是同样的操作,但它声称保证按照页面上指定的顺序执行脚本,并且这些脚本将在文档解析完成后执行。因此,一些脚本可能会在下载完成后等待后面下载但出现在它们之前的脚本。

不幸的是,由于标准之间的争执,defer的定义因规范而异,即使在最近的规范中也没有提供有用的保证。正如这里这个问题所示,浏览器以不同的方式实现defer:

  • 在某些情况下,一些浏览器存在错误,导致defer脚本按顺序运行。
  • 一些浏览器会延迟DOMContentLoaded事件,直到defer脚本加载完成,而另一些则不会。
  • 一些浏览器会遵循具有内联代码和没有src属性的<script>元素上的defer,而其他一些则会忽略它。

幸运的是,规范至少指定了async覆盖defer。因此,您可以将所有脚本视为async,并获得广泛的浏览器支持,如下所示:

<script defer async src="..."></script>

使用这种方法,全球98%的浏览器和美国99%的浏览器都可以避免阻止。

(如果您需要等待文档解析完成,请监听事件DOMContentLoaded或使用jQuery方便的.ready()函数。无论如何,您都希望在不实现defer的浏览器上优雅地回退。)


5
我认为这是不正确的。defer的好处在于它直到页面解析完成之前都不会执行。这个页面有一个很好的视觉展示,可以解释async和defer之间的区别:http://peter.sh/experiments/asynchronous-and-deferred-javascript-execution-explained/ - tinkerr
1
@tinkerr 从概念上讲,你是正确的;但在实践中并非如此。由于它并没有被一致地实现,因此序列保证并不普遍存在,也就不是保证。在实现时你需要关心执行效果。该设计的意图很好,但并没有特别有用。 - Chris Moschini
我想指出的是,Opera自15版本以来就支持defer属性,该版本于2013年6月2日发布。 - user1846065
为什么要同时使用 deferasync?难道我们不应该只使用其中一个吗?请详细说明 :) - Vikas Bansal
1
@VikasBansal 针对不支持async的旧浏览器,尤其是旧版IE。 - Chris Moschini

52

更新时间:2016年2月19日

考虑到这个答案已经过时,请参考本帖子中其他答案以获取更适用于较新浏览器版本的信息。


基本上,defer告诉浏览器在执行脚本块中的JavaScript之前“等待”。“通常,在DOM完成加载且document.readyState == 4之后进行此操作。

Defer属性仅适用于Internet Explorer。 在Windows 7上的Internet Explorer 8中,您在JS Fiddle测试页面中看到的结果为1-2-3。

结果可能因浏览器而异。

http://msdn.microsoft.com/en-us/library/ms533719(v=vs.85).aspx

与普遍认为的相反,实际上IE更经常遵循标准,“defer”属性在DOM Level 1规范http://www.w3.org/TR/REC-DOM-Level-1/level-one-html.html中已定义

W3C对defer的定义:http://www.w3.org/TR/REC-html40/interact/scripts.html#adef-defer

“当设置时,此布尔属性为用户代理提供一个提示,表明该脚本不会生成任何文档内容(例如,在JavaScript中没有“document.write”),因此,用户代理可以继续解析和呈现。”


9
如果你的答案已经过时,你应该编辑它,而不是在其他答案的评论中抱怨负评。负评是针对“无用”的答案的。 - Christian Conkle
10
我感谢您的礼仪教诲,但这里的其他答案都是最新的。我所指的是在问题被提出时,错误的答案没有被选中。也许你应该管理那些误传社区错误选择答案的人,而不是试图提醒人们事物随着时间而改变,而且背景很重要。我认为删除我的回答没有价值,因为历史信息也很有价值。 - Mark At Ramp51
3
“我认为删除我的回答没有价值,因为历史信息也是有价值的。” 那么,在这种情况下,是否考虑在开头加上一条注释,指出它只适用于HTML5之前的版本,并链接到“正确”的(最新的)答案? 这样做应该可以避免很多麻烦(作为曾经有一个“错误”答案被接受并最终“被同行压迫”更改的人来说)。 - mgibsonbr
3
@Leo 是否应该标记一下呢?通过在谷歌中搜索“html5 defer script”,这个回答排名第三。因此,这个回答会向众多用户提供过时和不正确的定义。(当前的定义是:“表示用户代理可以推迟脚本的处理。请参见 HTML 4.0 中 defer 属性的定义。”) - Malavos
2
@MarkAtRamp51,我认为你应该更新你的答案。任何发现这个问题和你的答案的人都不会认识它的历史信息。对他们来说,它看起来就像是今天正确的答案。这就是互联网的工作方式。因此,你应该编辑你的答案,注明它曾经是正确的,并引用正确的答案。 - Juuro

15

defer 只能用于包含外部脚本<script> 标签中,因此建议将其用于位于 <head> 部分的 <script> 标签中。


10

由于defer属性只能用在带有src的脚本标签上,因此找到了一种模拟内联脚本的延迟加载方式。使用DOMContentLoaded事件。

<script defer src="external-script.js"></script>
<script>
document.addEventListener("DOMContentLoaded", function(event) {
    // Your inline scripts which uses methods from external-scripts.
});
</script>
这是因为,在 defer 属性脚本完全加载后,DOMContentLoaded 事件才会触发。

8

defer 属性仅适用于外部脚本(仅在 src 属性存在时使用)。


6
请看谷歌开发者Jake Archibald在2013年撰写的这篇优秀文章,名为《深入探究脚本加载的神秘世界》(Deep dive into the murky waters of script loading)。文章链接:http://www.html5rocks.com/en/tutorials/speed/script-loading/
以下是该文章中相关的引用部分:

Defer

<script src="//other-domain.com/1.js" defer></script>
<script src="2.js" defer></script>

Spec says: Download together, execute in order just before DOMContentLoaded. Ignore “defer” on scripts without “src”.

IE < 10 says: I might execute 2.js halfway through the execution of 1.js. Isn’t that fun??

The browsers in red say: I have no idea what this “defer” thing is, I’m going to load the scripts as if it weren’t there.

Other browsers say: Ok, but I might not ignore “defer” on scripts without “src”.

(我要补充一点,早期版本的Firefox会在defer脚本运行完成之前触发DOMContentLoaded事件,根据这篇评论。)

现代浏览器似乎已经支持async了,但是你需要接受脚本可能在DOMContentLoaded事件之前无序地运行。


4

4

<script defer> -
当浏览器与带有defer属性的脚本标签交互时,

  1. 它开始获取脚本文件并同时解析HTML。
  2. 在这种情况下,只有在完成HTML解析后脚本才会被执行。

<script async>
当浏览器与带有async属性的脚本标签交互时,

  1. 它开始获取脚本文件并同时解析HTML。
  2. 但是当脚本完全获取后,它停止HTML解析,然后开始执行脚本,之后继续HTML解析。

<script>
当浏览器与脚本标签交互时,

  1. 它停止HTML解析,获取脚本文件,
  2. 在这种情况下,执行脚本,然后继续HTML解析

1
这个布尔属性的设置是为了告诉浏览器脚本应该在文档解析后执行。由于这个特性并未被所有主流浏览器实现,作者不能假设脚本的执行实际上会被延迟。不要从延迟脚本中调用document.write()(自从Gecko 1.9.2以来,这将破坏文档)。不应该在没有src属性的脚本上使用defer属性。自从Gecko 1.9.2以来,没有src属性的脚本上忽略defer属性。但是,在Gecko 1.9.1中,即使是内联脚本也会被延迟,如果设置了defer属性。
defer与chrome、firefox、ie>7和Safari一起使用
参考:https://developer.mozilla.org/en-US/docs/HTML/Element/script

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接