the script defer attribute

This post is by Olivier Rochard. Olivier does research at Orange Labs in France.

In HTML, the script element allows authors to include dynamic script in their documents. The defer attribute is boolean attribute that indicates how the script should be executed. If the defer attribute is present, then the script is executed when the page has finished parsing. The element is added to the end of the list of scripts that will execute when the document has finished parsing. Think about a FIFO processing queue : the first script element to be added to the queue will be the first script to be executed, then processing proceeds sequentially in the same order.

There is one very good reason for using the defer attribute: performance. If you include a script element in your HTML page the script must be evaluated immediately while the page is being parsed. This means that objects have to be created, styles must be flushed, etc. This can make page loading slower. The defer attribute implies that the script has no side effects on the document as it’s being loaded and can safely be evaluated at the end of the page load.

The defer attribute was first introduced in Internet Explorer 4, and added in the HTML 4 specification.

A simple test.

Here is a simple first test to see how the attribute works. The following lines are in the head element of a page:

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

If the defer attribute for the script element is correctly implemented the browser will:

  1. Render the page.
  2. Execute the second script element after all the others.
  3. Display “Test 1 : pass” on the Firebug console.

If the console displays “Test 1 : fail” it’s because the scripts are executed in the same order as in the source code.

Note that the correct syntax for XHTML documents is:


A more advanced test

This second test is a way to see how the feature works in a webpage with multiple script elements inserted:

  • Inline in the head and body elements
  • External via src attribute in head and body elements
  • With dynamic DOM insertion

Here is partial source code of a webpage that tests how defer affects script loading and parsing order:

‹!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›

When you click on the “Launch test 2” link in the document a pop-up appears with a list in it. This list shows the order of script elements loaded during the session.

The test also displays the DOMContentLoaded and body.onload events when they are fired.

If the defer attribute is correctly implemented in the browser, all the deferred lines should be near the bottom of the list.

Results of the second test for each browser are below (deferred scripts are in green color) :

  • The defer attribute behavior in the Firefox 3.5 browser is correct:
    1. Inline HEAD
    2. External HEAD (script2.js)
    3. Dynamic DOM insertion of a script (script3.js)
    4. Inline BODY
    5. External BODY (script8.js)
    6. Inline HEAD deferred
    7. External HEAD deferred (script1.js)
    8. Dynamic DOM insertion of a deferred script (script4.js)
    9. Inline BODY deferred
    10. External BODY deferred (script7.js)
    11. Deferred dynamic DOM insertion of a script (script5.js)
    12. Deferred dynamic DOM insertion of a deferred script (script6.js)
    13. DOMContentLoaded
    14. Body onLoad
  • The defer attribute behavior in the IE 8 browser is erratic: the order is different at each reload :
    1. Inline HEAD
    2. External HEAD (script2.js)
    3. Inline BODY
    4. External BODY (script8.js)
    5. Dynamic DOM insertion of a script (script3.js)
    6. Dynamic DOM insertion of a deferred script (script4.js)
    7. Inline HEAD deferred
    8. External HEAD deferred (script1.js)
    9. Inline BODY deferred
    10. External BODY deferred (script7.js)
    11. Body onLoad
    12. Deferred dynamic DOM insertion of a script (script5.js)
    13. Deferred dynamic DOM insertion of a deferred script (script6.js)
  • The defer attribute behavior in a WebKit browser (Safari 4.0) is erratic : the order is different at each reload :
    1. Inline HEAD deferred
    2. Inline HEAD
    3. External HEAD deferred (script1.js)
    4. External HEAD (script2.js)
    5. Inline BODY deferred
    6. Inline BODY
    7. External BODY deferred (script7.js)
    8. Deferred dynamic DOM insertion of a script (script5.js)
    9. Dynamic DOM insertion of a deferred script (script4.js)
    10. Deferred dynamic DOM insertion of a deferred script (script6.js)
    11. Dynamic DOM insertion of a script (script3.js)
    12. External BODY (script8.js)
    13. DOMContentLoaded
    14. Body onLoad
  • The defer attribute behavior in the Opera 10.00 Beta browser:
    1. Inline HEAD deferred
    2. Inline HEAD
    3. External HEAD deferred (script1.js)
    4. External HEAD (script2.js)
    5. Dynamic DOM insertion of a script (script3.js)
    6. Dynamic DOM insertion of a deferred script (script4.js)
    7. Deferred dynamic DOM insertion of a script (script5.js)
    8. Deferred dynamic DOM insertion of a deferred script (script6.js)
    9. Inline BODY deferred
    10. Inline BODY
    11. External BODY deferred (script7.js)
    12. External BODY (script8.js)
    13. DOMContentLoaded
    14. Body onLoad

We hope that this has been a useful introduction to how the defer attribute works in Firefox 3.5. The tests above will also help you predict behavior in other browsers as well.

Resources


13 comments

  1. N

    Hmmm… the sample HTML isn’t valid HTML, it does not use angle brackets but some weird “stylized” brackets. Someone made a big oopsie.

    June 27th, 2009 at 01:47

    1. Fawad Hassan

      This did so to prevent execution of code here

      September 27th, 2010 at 00:01

  2. K

    Probably WordPress “helping out”; it likes to do that with quotation marks too…

    June 27th, 2009 at 02:08

  3. Henrik Gemal

    BrowserSpy can also show you if defer is supported in your browser:
    http://browserspy.dk/javascript.php

    June 27th, 2009 at 12:35

  4. Havvy

    @N: Search and replace is useful in cases like that.

    @Blog: We really need to standardize the process of page loads.

    June 27th, 2009 at 16:04

  5. Mike Beltzner

    We need to convince Google Ads to switch their script so that it uses this tag, including Doubleclick. Need, need, need.

    June 29th, 2009 at 07:31

  6. henry

    Seems the post is invalid.
    See bug 518104 : [HTML5] Implement HTML5 changes to (Need to change so that defer is ignored on inline scripts)
    at
    https://bugzilla.mozilla.org/show_bug.cgi?id=518104

    December 31st, 2009 at 23:57

  7. Laurent CAPRANI

    The codes do not work in Firefox 3.6 anymore.
    It seems that @defer is disabled for inline scripts.

    December 4th, 2010 at 20:06

  8. Marcel Korpel

    @Laurent: that’s the correct behaviour (per HTML 5 spec). Contrary to what the articles says, FF 3.5’s behaviour is incorrect: `defer` on inline scripts should be ignored, so inline deferred scripts should be executed before external deferred scripts, even when those were included before the inline scripts.

    December 14th, 2010 at 15:18

  9. kn33ch41

    There should be a defer attribute for images, particularly considering that Opera is currently the only browser that actually fires DOMContentLoaded before images begin downloading; here is a bug report on Chrome that states the issue: http://code.google.com/p/chromium/issues/detail?id=7008

    December 21st, 2010 at 06:15

  10. Laurent CAPRANI

    @Marcel.
    Firefox 3.6 ignores defer for any flavor of HTML (HTML 4 or XHTML) that defines it and this is unfortunate.

    When they want to specify that a script interpretation must wait until the document is parsed, most developers rely on tricks like
    jQuery’s $(document).ready();
    onload events or
    -bottom .

    There definitely should be a uniform and declarative way. With defer introduced in Firefox 3.5, I thought it was it.

    December 24th, 2010 at 16:47

  11. Laurent CAPRANI

    Follow-up to the preceding comment

    &ellip; and there definetly should be a way to write HTML literals within these comments!

    In the preceding post, I wanted to mention «script» elements at the bottom of the «body» element.

    December 27th, 2010 at 12:45

  12. […] Currently I serve all javascripts combined in one large file via Amazon Cloudfront. But since jQuery is so large, I’m thinking about using the version provided by Google. Of course I would include both script tags in the bottom of the page and would add the defer attribute, if I had not read this article: http://hacks.mozilla.org/2009/06/defer/ […]

    February 21st, 2012 at 17:16

Comments are closed for this article.