脚本 defer 属性

这篇文章由 Olivier Rochard 撰写。Olivier 在法国的 Orange Labs 进行研究。

在 HTML 中,script 元素允许作者在他们的文档中包含动态脚本。defer 属性是一个布尔属性,它指示应该如何执行脚本。如果 defer 属性存在,则脚本在页面解析完成后执行。该元素被添加到文档解析完成后将执行的脚本列表的末尾。想想一个 FIFO 处理队列:添加到队列的第一个脚本元素将是第一个执行的脚本,然后处理按顺序进行,顺序相同。

使用 defer 属性有一个很好的理由:性能。如果您在 HTML 页面中包含一个 script 元素,则脚本必须在页面解析时立即进行评估。这意味着必须创建对象,必须刷新样式等。这会导致页面加载速度变慢。defer 属性意味着脚本在加载文档时对文档没有副作用,并且可以在页面加载结束时安全地评估。

defer 属性最初是在 Internet Explorer 4 中引入的,并在 HTML 4 规范 中添加。

一个简单的测试。

以下是一些简单的第一个测试,以了解属性的工作原理。以下行位于页面的 head 元素中

‹script›
	var test1 = "Test 1 : fail";
‹/script›
‹script defer›
  	console.log(test1);
‹/script›
‹script›
	test1 = "Test 1 : pass";
‹/script›

如果脚本元素的 defer 属性正确实现,浏览器将

  1. 渲染页面。
  2. 在所有其他脚本元素之后执行第二个脚本元素。
  3. 在 Firebug 控制台中显示“Test 1 : pass”。

如果控制台显示“Test 1 : fail”,则表示脚本按源代码中的顺序执行。

请注意,XHTML 文档的正确语法是


更高级的测试

第二个测试是查看功能如何在包含多个脚本元素的网页中工作的方式

  • headbody 元素中内联
  • 通过 src 属性在 headbody 元素中外部
  • 使用动态 DOM 插入

以下是一个测试 defer 如何影响脚本加载和解析顺序的网页的部分源代码

‹!doctype html›
‹html›
    ‹head›
        ‹title› Test 2 ‹/title›
        ‹script› var test2 = "Test 2 :nn"; ‹/script›
        ‹script› document.addEventListener("DOMContentLoaded",
                function(){
                        test2 += "tDOMContentLoadedn";
                }, false);
        ‹/script›
        ‹script defer› test2 += "tInline HEAD deferredn"; ‹/script›
        ‹script› test2 += "tInline HEADn"; ‹/script›
        ‹script src="script1.js" defer›
                // External HEAD deferred (script1.js)
        ‹/script›
        ‹script src="script2.js"›
                // External HEAD  (script2.js)
        ‹/script›
	‹script›
            // Dynamic DOM insertion of a script (script3.js)
            head = document.getElementsByTagName('head')[0];
            script3 = document.createElement('script');
            script3.setAttribute('src', 'script3.js');
            head.appendChild(script3);
            // Dynamic DOM insertion of a deferred script (script4.js)
            script4 = document.createElement('script');
            script4.setAttribute('defer', 'defer');
            script4.setAttribute('src', 'script4.js');
            head.appendChild(script4);
	‹/script›
	‹script defer›
            // Deferred dynamic DOM insertion of a script (script5.js)
            head = document.getElementsByTagName('head')[0];
            script5 = document.createElement('script');
            script5.setAttribute('src', 'script5.js');
            head.appendChild(script5);
            // Deferred dynamic DOM insertion of a deferred script
            // (script6.js)
            script6 = document.createElement('script');
            script6.setAttribute('defer', 'defer');
            script6.setAttribute('src', 'script6.js');
            head.appendChild(script6);
	‹/script›
    ‹/head›
    ‹body onload="test2 += 'tBody onLoadn';"›
        ‹script defer› test2 += "tInline BODY deferredn"; ‹/script›
        ‹script› test2 += "tInline BODYn"; ‹/script›

	... other body content ...

		Launch test 2

	... other body content ...

        ‹script src="script7.js" defer›
                // External BODY deferred (script7.js)
        ‹/script›
        ‹script src="script8.js"›
                // External BODY (script8.js)
        ‹/script›
    ‹/body›
‹/html›

当您单击文档中的“启动测试 2”链接时,将出现一个弹出窗口,其中包含一个列表。此列表显示了会话期间加载的脚本元素的顺序。

该测试还显示了 DOMContentLoadedbody.onload 事件何时触发。

如果浏览器中正确实现了 defer 属性,则所有延迟行都应位于列表的底部。

每个浏览器的第二个测试结果如下(延迟脚本以绿色显示)

  • deferFirefox 3.5 浏览器的属性行为正确:
    1. 内联 HEAD
    2. 外部 HEAD(script2.js)
    3. 动态 DOM 插入脚本(script3.js)
    4. 内联 BODY
    5. 外部 BODY(script8.js)
    6. 内联 HEAD 延迟
    7. 外部 HEAD 延迟(script1.js)
    8. 动态 DOM 插入延迟脚本(script4.js)
    9. 内联 BODY 延迟
    10. 外部 BODY 延迟(script7.js)
    11. 延迟动态 DOM 插入脚本(script5.js)
    12. 延迟动态 DOM 插入延迟脚本(script6.js)
    13. DOMContentLoaded
    14. Body onLoad
  • IE 8 浏览器中的 defer 属性行为不稳定:每次重新加载时顺序都不一样 
    1. 内联 HEAD
    2. 外部 HEAD(script2.js)
    3. 内联 BODY
    4. 外部 BODY(script8.js)
    5. 动态 DOM 插入脚本(script3.js)
    6. 动态 DOM 插入延迟脚本(script4.js)
    7. 内联 HEAD 延迟
    8. 外部 HEAD 延迟(script1.js)
    9. 内联 BODY 延迟
    10. 外部 BODY 延迟(script7.js)
    11. Body onLoad
    12. 延迟动态 DOM 插入脚本(script5.js)
    13. 延迟动态 DOM 插入延迟脚本(script6.js)
  • WebKit 浏览器(Safari 4.0)中的 defer 属性行为不稳定 : 每次重新加载时顺序都不一样 
    1. 内联 HEAD 延迟
    2. 内联 HEAD
    3. 外部 HEAD 延迟(script1.js)
    4. 外部 HEAD(script2.js)
    5. 内联 BODY 延迟
    6. 内联 BODY
    7. 外部 BODY 延迟(script7.js)
    8. 延迟动态 DOM 插入脚本(script5.js)
    9. 动态 DOM 插入延迟脚本(script4.js)
    10. 延迟动态 DOM 插入延迟脚本(script6.js)
    11. 动态 DOM 插入脚本(script3.js)
    12. 外部 BODY(script8.js)
    13. DOMContentLoaded
    14. Body onLoad
  • deferOpera 10.00 Beta 浏览器的属性行为:
    1. 内联 HEAD 延迟
    2. 内联 HEAD
    3. 外部 HEAD 延迟(script1.js)
    4. 外部 HEAD(script2.js)
    5. 动态 DOM 插入脚本(script3.js)
    6. 动态 DOM 插入延迟脚本(script4.js)
    7. 延迟动态 DOM 插入脚本(script5.js)
    8. 延迟动态 DOM 插入延迟脚本(script6.js)
    9. 内联 BODY 延迟
    10. 内联 BODY
    11. 外部 BODY 延迟(script7.js)
    12. 外部 BODY(script8.js)
    13. DOMContentLoaded
    14. Body onLoad

我们希望这篇文章对 defer 属性在 Firefox 3.5 中的工作原理有所帮助。上面的测试也将帮助您预测其他浏览器的行为。

资源


13条评论

  1. N

    嗯……示例 HTML 不是有效的 HTML,它没有使用尖括号,而是使用了一些奇怪的“样式化”括号。有人犯了一个大错误。

    2009年6月27日 凌晨 1:47

    1. Fawad Hassan

      这样做是为了防止代码在此处执行

      2010年9月27日 凌晨 0:01

  2. K

    可能是 WordPress“帮助”;它喜欢对引号做同样的事情……

    2009年6月27日 凌晨 2:08

  3. Henrik Gemal

    BrowserSpy 还可以显示您的浏览器是否支持 defer
    http://browserspy.dk/javascript.php

    2009年6月27日 下午 12:35

  4. Havvy

    @N: 在这种情况下,搜索和替换很有用。

    @博客:我们真的需要标准化页面加载流程。

    2009年6月27日 下午 4:04

  5. Mike Beltzner

    我们需要说服 Google Ads 切换他们的脚本,以便它使用此标签,包括 Doubleclick。需要,需要,需要。

    2009年6月29日 上午 7:31

  6. henry

    看来这篇文章无效。
    请参见错误 518104: [HTML5] 实现 HTML5 对 (需要更改以便在内联脚本上忽略 defer)

    https://bugzilla.mozilla.org/show_bug.cgi?id=518104

    2009年12月31日 下午 11:57

  7. Laurent CAPRANI

    代码在 Firefox 3.6 中不再起作用。
    看来 @defer 对内联脚本被禁用了。

    2010年12月4日 下午 8:06

  8. Marcel Korpel

    @Laurent: 这是正确的行为(根据 HTML 5 规范)。与文章所说相反,FF 3.5 的行为是错误的:内联脚本上的 `defer` 应该被忽略,因此内联延迟脚本应该在外部延迟脚本之前执行,即使那些脚本是在内联脚本之前包含的。

    2010年12月14日 下午 3:18

  9. kn33ch41

    应该有一个用于图像的 defer 属性,特别是考虑到 Opera 目前是唯一一个在图像开始下载之前实际触发 DOMContentLoaded 的浏览器;以下是关于 Chrome 的错误报告,其中说明了这个问题:http://code.google.com/p/chromium/issues/detail?id=7008

    2010年12月21日 上午 6:15

  10. Laurent CAPRANI

    @Marcel。
    Firefox 3.6 忽略了定义它的任何风格的 HTML(HTML 4 或 XHTML)的 defer,这很不幸。

    当他们想要指定脚本解释必须等到文档解析完毕时,大多数开发者依赖于像
    jQuery 的 $(document).ready();
    onload 事件或
    -bottom .

    一定应该有一种统一且声明性的方法。随着 defer 在 Firefox 3.5 中引入,我认为它就是这样。

    2010年12月24日 下午 4:47

  11. Laurent CAPRANI

    对前面评论的跟进

    &ellip; 并且在这些评论中一定应该有一种编写 HTML 字面量的方法!

    在之前的帖子中,我想提到“body”元素底部的“script”元素。

    2010年12月27日 下午 12:45

  12. […] 目前,我通过 Amazon Cloudfront 将所有 JavaScript 脚本合并到一个大型文件中。但由于 jQuery 很大,我正在考虑使用 Google 提供的版本。当然,我会将两个脚本标签都包含在页面的底部,如果我没有读过这篇文章,我会添加 defer 属性:https://hacks.mozilla.ac.cn/2009/06/defer/ […]

    2012年2月21日 下午 5:16

这篇文章的评论已关闭。