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:
- Render the page.
- Execute the second script element after all the others.
- 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
andbody
elements - External via
src
attribute inhead
andbody
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:
- Inline HEAD
- External HEAD (script2.js)
- Dynamic DOM insertion of a script (script3.js)
- Inline BODY
- External BODY (script8.js)
- Inline HEAD deferred
- External HEAD deferred (script1.js)
- Dynamic DOM insertion of a deferred script (script4.js)
- Inline BODY deferred
- External BODY deferred (script7.js)
- Deferred dynamic DOM insertion of a script (script5.js)
- Deferred dynamic DOM insertion of a deferred script (script6.js)
- DOMContentLoaded
- Body onLoad
- The defer attribute behavior in the IE 8 browser is erratic: the order is different at each reload :
- Inline HEAD
- External HEAD (script2.js)
- Inline BODY
- External BODY (script8.js)
- Dynamic DOM insertion of a script (script3.js)
- Dynamic DOM insertion of a deferred script (script4.js)
- Inline HEAD deferred
- External HEAD deferred (script1.js)
- Inline BODY deferred
- External BODY deferred (script7.js)
- Body onLoad
- Deferred dynamic DOM insertion of a script (script5.js)
- 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 :
- Inline HEAD deferred
- Inline HEAD
- External HEAD deferred (script1.js)
- External HEAD (script2.js)
- Inline BODY deferred
- Inline BODY
- External BODY deferred (script7.js)
- Deferred dynamic DOM insertion of a script (script5.js)
- Dynamic DOM insertion of a deferred script (script4.js)
- Deferred dynamic DOM insertion of a deferred script (script6.js)
- Dynamic DOM insertion of a script (script3.js)
- External BODY (script8.js)
- DOMContentLoaded
- Body onLoad
- The defer attribute behavior in the Opera 10.00 Beta browser:
- Inline HEAD deferred
- Inline HEAD
- External HEAD deferred (script1.js)
- External HEAD (script2.js)
- Dynamic DOM insertion of a script (script3.js)
- Dynamic DOM insertion of a deferred script (script4.js)
- Deferred dynamic DOM insertion of a script (script5.js)
- Deferred dynamic DOM insertion of a deferred script (script6.js)
- Inline BODY deferred
- Inline BODY
- External BODY deferred (script7.js)
- External BODY (script8.js)
- DOMContentLoaded
- 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
- The fixed bug 28293 – (defer) scripts with defer attribute not deferred : bug 28293
- The “JavaScript: Defer Execution” article on the WebsiteOptimization website : http://www.websiteoptimization.com/speed/tweak/defer/
- The script article on Mozilla Developer Center : https://developer.mozilla.org/En/HTML/Element/Script
- The “script element” part of the HTML 5 Draft : http://www.whatwg.org/specs/web-apps/current-work/#script
- The “Parsing HTML documents : the end” part of the HTML 5 Draft : http://www.whatwg.org/specs/web-apps/current-work/#the-end
13 comments