1

I'm reading this article about document.write, and it states that:

Sometimes the scripts are added by the document.write. Don’t use this method, as the rest of the page will await for script loading and execution.

If the remote server hands up, the page will may take too much to load.

I don't really understand the difference between using document.write and DOM API in terms of blocking. I've run a simple experiment in Chrome with the following <head> blocks:

1) Using document.write:

<head>
    <script src="index-1.js"></script>
    <script>
      var url = 'index-2.js'
      document.write('<script src="'+url+'"></scr'+'ipt>');
    </script>
    <link rel="stylesheet" href="index.css">
</head>

2) Using DOM API:

<head>
    <script src="index-1.js"></script>
    <script>          
      var script = document.createElement('script');
      script.src = 'index-2.js';
      document.documentElement.firstChild.appendChild(script);
    </script>
    <link rel="stylesheet" href="index.css">
</head>

In both cases Chrome waits for the script index-1.js to execute before loading index-2.js. Here is the picture:

enter image description here

So I'm confused. Am I doing or understanding something wrong?

EDIT:

If I add third script tag after link tag

<link rel="stylesheet" href="index.css">
<script src="index-3.js"></script>

Then I get the following results:

enter image description here

Max Koretskyi
  • 101,079
  • 60
  • 333
  • 488
  • document.write()-added script behave like hard-coded ones; sync. in the pics/code shown, you don't have any script coming after index-2 to tell if it's sync or not, but if you did, the DOM one would look like the CSS file: starting at the same time as index-2.js, and the document.write() one would look like index-1: pausing all scripts after it. – dandavis Mar 24 '16 at 08:20
  • 1
    _I'm reading this article.._ What article? – hindmost Mar 24 '16 at 08:24
  • @hindmost, updated – Max Koretskyi Mar 24 '16 at 08:46

2 Answers2

2

In the first code example, the browser waits for index-2.js to load and execute before running ANY further scripts and before displaying your page. This is said to be a "blocking" load. It blocks further progress by the parser until the script is loaded.

In the second code example (the script inserted with .appendChild()), the browser does not wait for index-2.js to load or run before proceeding with other scripts or before displaying your page. Here index-2.js is loaded asynchronously while other things in the page continue to be processed. This is said to be a "non-blocking" load. It does not block further progress by the browser while it is loading. Once it finishes loading the dynamic script in the background, the browser is free to execute it whenever it wants to (likely when it has nothing else to do at the moment). When it does actually execute it will vary by browser and is not governed by specification.

So, the timing has nothing to do with index-1.js. That is a synchronous script tag. It is blocking in both examples. The browser will not process past that <script> tag.

The difference is in what happens after you insert index-2.js.

Execution order is what is defined by specification so that is what you have to concentrate on or measure. It is up to the browser when it actually decides to load a script over the network so you can't use the loading bar chart to see the execution. Obviously, it can't start loading it before the parser knows about the script and it has to load it before it is required to execute it, but within that, the browser can use its own logic to decide when to load it. Because each browser has limits on the number of simultaneous resources that it will attempt to download from one host, exactly when it decides to download the script could easily vary from one browser to the next.

And, keep in mind that a non-blocking script inserted with .appendChild() can run any time the browser wants. It can run nearly immediately (if the browser has that script in its cache and has nothing else to do because it's waiting on other resources) or it can run it near the end of the whole loading process. When you insert it with .appendChild(), you are instructing the browser not to block the current parsing and loading of the page to run this script, but as soon as the browser has the contents of that script, it can run it whenever it feels like it.

When you insert it with document.write(), you are instructing the browser to run it as soon as the current script tag is finished running which obviously means it has to be loaded immediately too.


If you put this:

<script src="index-3.js"></script>

right before the <link> tag, then your timeline would tell the whole story as you would see index-3.js run sooner in the second code example because it would not wait for index-2.js to load and run. In fact, if you put console.log() entries in each of index-1.js, index-2.js and index-3.js, then you could see the difference in execution order.


FYI, you can also use the async and defer attributes in <script> tags to make an inline <script> tag be non-blocking. More discussion of those options here: load and execute order of scripts.

Community
  • 1
  • 1
jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • thanks, I just tried adding 3rd script, and it seems that Chrome starts loading index-3.js along with index-1.js and doesn't wait for `index-2.js` to execute. Please see my edit. – Max Koretskyi Mar 24 '16 at 10:39
  • @Maximus - Yes, that is what I explained and is as expected. When inserted with `.appendChild()`, then `index-2.js` will be loaded and run in a non-blocking asynchronous fashion and can be run later than `index-3.js`. – jfriend00 Mar 24 '16 at 11:02
  • I see, but the behavior is the same whether I'm using `document.write` or `DOM API` - that's what confusing – Max Koretskyi Mar 24 '16 at 11:08
  • @Maximus - I think something is wrong with your measuring. Perhaps you're getting confused by cached script files? – jfriend00 Mar 24 '16 at 11:22
  • @Maximus - In my own testing, I do not get the same results as you are saying in your latest comment. I see scripts executing inline and in order when inserted with `document.write()`. – jfriend00 Mar 24 '16 at 11:32
  • weird... can you please share the code and the environment details? I'll try to reproduce. This issue is driving me crazy and I want to get a firm grip on the subject since I've spent already considerable amount of time on this matter – Max Koretskyi Mar 24 '16 at 11:45
  • If I add logging with script name, I indeed see the difference in the execution order between `document.write` and `DOM API`: with `document.write` the 3rd script is executed after the 2nd, where is with `DOM API` the 3rd is executed before the 2nd. So is the difference in execution order? – Max Koretskyi Mar 24 '16 at 11:53
  • @Maximus - Execution order is what is defined per specification. It is really up to the browser when it actually fetches something over the network (except that it can't start fetching it before the script tag is inserted either by code or by the parser and it has to fetch it before it can execute it - but within that, it's up to the browser). And, loading may depend upon how many other resources are being loaded at the time (since browsers request a max number of resources from the same host at the same time and a `.appendChild()` inserted script can be loaded later if the browser is busy). – jfriend00 Mar 24 '16 at 11:59
  • Thanks, can you please add this information to the answer, so that I can accept it? – Max Koretskyi Mar 24 '16 at 12:57
  • one more question, does DOM API insert script tag after domcontentloaded event? – Max Koretskyi Mar 24 '16 at 13:28
  • 1
    @Maximus - My understanding is that a script inserted with the DOM API can run ANY TIME. It can run immediately or it can wait awhile. In my testing of three browsers, I saw it both before and after the `DOMContentLoaded` event. FYI, once you have your scripts instrumented with `console.log()` statements, it takes only a few seconds to add your own event handler and `console.log()` for when the `DOMContentLoaded` event fires to see for yourself. – jfriend00 Mar 24 '16 at 16:08
  • 1
    @Maximus - I added a bunch more info to my answer. – jfriend00 Mar 24 '16 at 16:19
  • thanks a lot, now the answer looks elaborate and full :) – Max Koretskyi Mar 24 '16 at 16:32
1

The problem is that, a browser need to execute scripts in the order they are defining in the page. Traditionally browser where doing this:

  • read & parse HTML up to script 1
  • download script 1
  • parse script 1
  • execute script 1
  • read & parse HTML up to script 2
  • download script 2
  • parse script 2
  • execute script 2
  • ...

As this is terribly inefficient, browser started to do speculative parsing. The actions are now

  • read & parse HTML up to the end
  • download & parse all resources (script, css, images)
  • execute script1
  • execute script2
  • ...

document.write is used to write in the HTML as the browser is parsing it. When you use that to add a script, the browser need to execute your script now to keep the correct ordering. So it will start downloading, parsing and executing your script while the resources that were downloaded previously are just waiting.

If you use the document.createElement method, you are adding a script to execute after the rest of the page is loaded. So the browser will download your script while it's continuing to render the page and execute it right after.

vmeurisse
  • 70
  • 5