Javascript Load Order with Defer Attribute

HTML rendering and Javascript are sometimes mystery to me, and usually I can only understand how something works by experimenting. The implementation differs quite a bit between MSIE, Mozilla, Netscape 4 and other commonly used browsers as well, and it can be quite frustrating sometimes. And when the outcome of a script differs from its specification, it then really annoys me.

I was playing with some Javascript loading order test this morning, and here's a simple page that I used for the test.

  <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
  <html>
    <head>
      <script type="text/javascript">
        function handleOnLoad() {
          alert('On load');
        }
        alert('Head');
      </script>
    </head>
    <body onload="handleOnLoad()">
      <script type="text/javascript">
        alert('Body 1');
      </script>
      <script type="text/javascript" defer="true">
        alert('Body 2');
      </script>
      <script type="text/javascript">
        alert('Body 3');
      </script>
      <h1>Javascript Test.</h1>
    </body>
  </html>

The order of alter() triggered is:

  • Head
  • Body 1
  • Body 2
  • Body 3
  • On load

Well. There is nothing surprising, except for "Body 2" that is executed and triggered between "Body 1" and "Body 3", even though defer="true" attribute has been set. According to some Javascript FAQ and W3C's own HTML 4 reference, defer="true" attribute is designed to execute scripts asynchronously, i.e. after the document has been parsed. Therefore I was expecting "Body 2" to be displayed in between "Body 3" and "On load", or even after "On load" as it might be appended to the end of the message queue. But obviously the experiment says it is not, and I think I might need to go back fixing some of my other scripts as I always assume so. I guess according to the reference, defer="true" only provides as a hint that it is not going to affect the document structure, but the browser implementation does not need to accept the hint.

By the way, it is tested on both MSIE 6SP1 and Mozilla 1.4, and in the case of MSIE, the alert dialog box of "Body 2" is actually around 50 pixels lower and further to the right, comparing with other alerts triggered. On Mozilla 1.4, there is no difference.

Updated at 11:58am: The example above does generate a different result if the deferred Javascript is fetched from else where...

Here is some changes to the HTML document:

      <script type="text/javascript">
        alert('Body 1');
      </script>
      <script type="text/javascript" defer="true" src="test.js">
      </script>
      <script type="text/javascript">
        alert('Body 3');
      </script>

Instead of doing an alert() to print out "Body 2", it loads an external Javascript file named "test.js". And the content of that file is simply...

  alert("Body 2");

Now, Mozilla 1.4 will continue to print out "Body 1", "Body 2" and "Body 3" in order, but MSIE 6SP1 will honour the defer="true" attribute and print out "Body 1", "Body 3" and "Body 2", and then finally "On Load". If defer="true" attribute is not specified, then both Mozilla and MSIE will bring out the alerts in the order they are in the document.

I think MSIE's behaviour is actually correct by asynchronously deferring operations that might be blocked on IO. I guess it makes more sense to Win32 developers as they are used to event driven networking programming. That also implies that MSIE might finish parsing the main document faster than Mozilla if there are a lot of external scripts linked over slow network. Obviously, parsing HTML document and displaying them involves much more than the network programming model, but here's just a little thought on the implication.

Back to more coding...