Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why script finishes before Browser displays Page

Tags:

javascript

In the code below, why does the loop of console.log finish before any HTML element is displayed? I have placed the JavaScript code at the end of HTML file.

<!DOCTYPE html>
<html lang="en">

<body>

    <p id="counter"> no clicks yet </p>
    <script>
        for (i = 0; i < 99999; ++i) {
            console.log(i);
        }
        console.log("ready to react to your clicks");
    </script>
</body>

</html>

Update:

based on one answer I tried this and still HTML doc gets displayed only after console log loop is fully executed:

<html lang="en">

<body onload="onLoad()">
    <button onclick="clickHandler()">Click me</button>
    <p id="counter"> no clicks yet </p>
    <script>
        var counter = 0;
        function clickHandler() {
            counter++;
            document.getElementById("counter").innerHTML = "number of clicks:" + counter;
        }
        function onLoad() {
            for (i = 0; i < 99999; ++i) {
                console.log(i);
            }
            console.log("ready to react to your clicks");
        }

    </script>
</body>

</html>
like image 546
Waterfr Villa Avatar asked Sep 28 '18 15:09

Waterfr Villa


2 Answers

You're asking different questions. Your title asks:

Why console.log gets executed before DOM model being generated?

What I understand by "before DOM model being generated" is "before the p element is created". The p element is created before your script element runs, and it is accessible from it. If you do document.getElementById("counter") in your script, you will get your paragraph.

Then you ask:

why does the loop of console.log finish before any HTML element is displayed?

This is a different question. The issue here is not that the parsing has stopped (contrarily to what Simeon Stoykov suggested). It is true that the parsing stopped, but it does not explain why the paragraph is not shown. The browser could show the paragraph by the time script is executed. (Actually, if you put a breakpoint inside the script element, Chrome will show the paragraph as soon as the breakpoint is hit.)

What is happening is that the browser is applying optimizations. The browser delays as much as possible reflow (calculating the position and geometry of elements on the page) and rendering of the elements. The goal is to reduce the total time spent on reflow and rendering. Suppose instead of having a script that dumps numbers to the console, you have a script that changes the style of your paragraph, which in turn causes the paragraph to change position or size. A naive browser might do this:

  1. Immediately reflow and render p#counter.
  2. Execute the script, which updates the styles such that the old position and size of p#counter changes.
  3. Reflow and render p#counter to reflect the changes.

Real browsers like FF or Chrome will skip the first step above and will only reflow and render p#counter once instead of doing it twice.

In a trivial example like yours, the optimization is not great but imagine a complex page with a bunch of tables and a starting script that immediately fills the tables with rows of data, images, links, etc. Being able to reduce X number of reflow-render operations, to just one reflow-render makes a huge difference.

In the example you gave, the browser knows that the user cannot do anything with the page while your script is executing, so there's no point in the browser rendering the page until your script is done.

At this point the question arise:

If the browser is delaying computing element position and size, then why can I do things like document.getElementById("counter").getBoundingClientRect() in my script and get coordinates??

The browser can delay it as much as possible. When JavaScript code queries the position or the size of an element, then it is no longer possible to delay. The browser must do a reflow right there and then to give the answer. (In my discussion above, I've talked of reflow and render together to simplify a bit. But reflows are not necessarily immediately followed by a render. So the render may still be delayed until after the script has finished executing.)


In the off-chance that your example was meant to represent a long computation that blocks off rendering and prevents your user from getting any feedback, the way to get around that is to break the lengthy work into chunks: perform a chunk of work, then use setTimeout to schedule the next chunk, and so on until the whole work is done. For instance:

const p = document.getElementById("counter");
let i = 0;
const limit = 100;
const chunkSize = 10;
function doWork() {
  for (let thisChunk = 0; thisChunk < chunkSize && i < limit; thisChunk++) {
    console.log(i++);
    p.textContent = i;
  }
  if (i < limit) {
    setTimeout(doWork, 0);
  }
}
doWork();

In the example above, a chunk of 10 numbers is executed, then setTimeout schedules the next chunk and the script returns control to the JavaScript event loop, which performs the tasks needed. This include responding to user interactions, and thus the page is rendered to the user.

In some cases it may make sense to offload the whole work into a WebWorker.

like image 200
Louis Avatar answered Oct 20 '22 01:10

Louis


There are engines for HTML render like Blink in Chrome and engines to compiling javascript like V8 in Chrome.

The render process have 4 stages :

  • Constructing the DOM tree: parsing the HTML document and converting the parsed elements to actual DOM nodes in a DOM tree.

  • Constructing the CSSOM tree : CSSOM refers to the CSS Object Model, the browser encountered a link tag while Constructing the DOM tree to adjust DOM tree depending on CSS.

  • Constructing the render tree : The visual instructions in the HTML, combined with the styling data from the CSSOM tree, are being used to create a render tree.

  • Layout of the render tree : When the renderer is created and added to the tree, it does not have a position and size. Calculating these values is called layout.

  • Painting the render tree : In this stage, the renderer tree is traversed and the renderer’s paint() method is called to display the content on the screen.

the scripts are parsed and executed when the engine reached the tag, document parsing stopped until parsing scripts done, if there is changes in the DOM the engine will apply it on stage 4 (Layout of the render tree) before stage 5.

It waits the console.log done executing then show DOM not just executing it before showing DOM.

see : How JavaScript works: the rendering engine and tips to optimize its performance

like image 41
hasen2009 Avatar answered Oct 20 '22 01:10

hasen2009