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>
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:
p#counter
.p#counter
changes.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
.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With