Add Page Content Efficiently
Using A Loop To Add Content
In the last lesson, we used afor
loop to create two hundred paragraphs, add event listeners to them, and add them to the page. Let's take another look at thefor
loop, but this time without all of the event listener stuff:
for (let i = 1; i <= 200; i++) {
const newElement = document.createElement('p');
newElement.textContent = 'This is paragraph number ' + i;
document.body.appendChild(newElement);
}
Reflect
Is thisfor
loop code written efficiently? Write out the different ways you think this code could be improved. (hint - there are actually several ways!)
SUBMIT
Let's quickly recap the code to see where we can make improvements.
for (let i = 1; i <= 200; i++) {
const newElement = document.createElement('p');
newElement.textContent = 'This is paragraph number ' + i;
document.body.appendChild(newElement);
}
This code:
- creates a paragraph element
- adds some text to the paragraph
- adds the paragraph to the page
...and it does this two hundred times.
Since we want two hundred things done, the best way to do this is with afor
loop, so that code is inescapable. However, the code_inside thefor
loop_is not all that efficient, and there are quite a few things we could do to improve this code. We could:
- create some parent container element outside of the loop
- we could append all new paragraph elements to this parent container
- we append this parent container to the
<body>
element instead of appending each time through the loop
Let's see all of these changes:
const myCustomDiv = document.createElement('div');
for (let i = 1; i <= 200; i++) {
const newElement = document.createElement('p');
newElement.innerText = 'This is paragraph number ' + i;
myCustomDiv.appendChild(newElement);
}
document.body.appendChild(myCustomDiv);
This looks a lot better, right? Right!...but how do we_know_it is? How can we_prove_it's better?
We can test the time it takes to actually run this code!
Testing Code Performance
The standard way to measure how long it takes code to run is by usingperformance.now()
.performance.now()
returns a timestamp that is measured in milliseconds, so it's extremely accurate. How accurate? Here's what the its documentation page says:
accurate to five thousandths of a millisecond (5 microseconds)
That's_incredibly_precise!
If you've ever used a timing procedure in another programming language, then you might've heard of Epoch time (also called Unix time or POSIX time). These tools tell you the time that has passed since 1/1/1970 (the first of January). The browser'sperformance.now()
method is slightly different in that it starts measuring from the time the page loaded. Detailed information can be found on its documentation page:performance.now() on MDN
These are the steps to useperformance.now()
to measure the speed of your code:
- use
performance.now()
to get the an initial start time for the code - run the code you want to test
- execute
performance.now()
to get another time measurement - subtract the initial time from the final time
Adding two hundred paragraphs to the page is actually going to be relatively quick, so let's slow things down by using a set of nestedfor
loops that just count from one to a hundred...one hundred times!
for (let i = 1; i <= 100; i++) {
for (let j = 1; j <= 100; j++) {
console.log('i and j are ', i, j);
}
}
Next, we'll add in theperformance.now()
code to measure how long these loops take:
const startingTime = performance.now();
for (let i = 1; i <= 100; i++) {
for (let j = 1; j <= 100; j++) {
console.log('i and j are ', i, j);
}
}
const endingTime = performance.now();
console.log('This code took ' + (endingTime - startingTime) + ' milliseconds.');
Usingperformance.now()
to calculate the total time it takes code to run.
Let's go back to our original code of adding two hundred paragraphs to the page to see how long that code takes to run.
Using a Document Fragment
So far, we've made a number of improvements to the code. However, there is still one thing that seems not so great; we have to create a extraneous<div>
element_just to hold all the<p>
tags so we can add them all at once_and then we append this<div>
to the<body>
element. So in the end, we have an extra<div>
that isn't really needed. It was just around because we wanted to add each new<p>
to it instead of to the<body>
.
Why are we doing this? The browser is constantly working to make the screen match the DOM. When we add a new element, the browser has to run through areflow
calculation (to determine the new screen layout) and thenrepaint
the screen. This takes time.
If we had added each new paragraph to the body element, then the code would've been a lot slower, because this would cause the browser to go through the reflow and repaint processfor each paragraph. We really only want the browser to do this once, so we need to attach each new paragraph to something, but we don't want to have an extra, unneeded element to get added to the DOM.
This is exactly why we have theDocumentFragment! According to the documentation, a DocumentFragment:
represents a minimal document object that has no parent. It is used as a lightweight version of Document that stores a segment of a document structure comprised of nodes just like a standard document.
So it's like creating another lightweight DOM tree. But the beneficial part of this is what it says next:
The key difference is that because the document fragment isn't part of the active document tree structure, changes made to the fragment don't affect the document, cause reflow, or incur any performance impact that can occur when changes are made.
In other words, changes made to a DocumentFragment happen off-screen; there's no reflow and repaint cost while you build this. So this is exactly what we need!
We can use the.createDocumentFragment()
method to create an empty DocumentFragment object. This code should be very familiar to you, because it looks so very similar todocument.createElement()
.
const myDocFrag = document.createDocumentFragment();
Let's rewrite our code to use a DocumentFragment instead of the<div>
.
const fragment = document.createDocumentFragment(); // ← uses a DocumentFragment instead of a <div>
for (let i = 0; i < 200; i++) {
const newElement = document.createElement('p');
newElement.innerText = 'This is paragraph number ' + i;
fragment.appendChild(newElement);
}
document.body.appendChild(fragment); // reflow and repaint here -- once!
Recap
In this section, we took a brief dive into the performance implications of the code we write. We looked at a specific chunk of code and came up with ways that we could improve its performance simply by rearranging when the code was running (moving initialization code out of thefor
loop).
We also looked at how to measure how long it takes code to run usingperformance.now()
.
Lastly, we looked at using a DocumentFragment to prevent performance issues and to prevent adding unnecessary elements to the DOM.