Performance

Performance

Add Page Content Efficiently

Let's think of how we can make this for loop more effictient:

  • Creates a paragraph
  • Adds text to the paragraph
  • Adds the paragraph to page
  • Repeats 200 times

We do want two hundred paragraphs created; The for loop is what should be used. Inside the for loop is where performance is lost. To improve the performance:

  • Create some parent container element outside of the for loop
  • Append all new paragraph elements to parent container>
  • Append the parent container to the <body> element instead of appending each time through the loop

    const customDiv = document.createElement('div');            
    //holds the new 'p' elements                                
    for (let i = 1; i <= 200; i++) {                            
        const newElement.creatElement('p');                     
        newElement.innerText = "This is paragraph number " + i; 

        customDiv.appendChild(newElement);                      
    }                                                           
    document.body.appendChild(customDiv);                       

To prove this is more performant, we can test the time it takes to actually run the code.

Testing Code Performance

performance.now() returns a timestamp that is measured in milliseconds
Accurate to five thousandths of a millisecond (5 microseconds)
MDN | Performance: now() method

To use performance.now():

  1. use proformance.now() to get the initial start time for the code
  2. run the code to test
  3. Execute performance.now() to get another time measurement
  4. subtract the initial time from the final time

Adding a nested for loop will slow things down so we can see how this works


    const startTime = performance.now();                    
    for (let i = 1; i <= 100; i++){                         
        for (let j = 1; j<=100; j++){                       
            console.log("j = " + j + " | i = " + i);        
        }                                                   
    }                                                       
    const endTime = performance.now();                      
    console.log("Time to run: " + (endTime - startTime));   

We can use our previous code example to test this and PROVE it's faster:

There is dat.now() method, which essentially works the same as performance.now() but it's less percise. date.now() returns time in millisecond. performance.now() returns time in microseconds.

Using a Document Fragment

A DocumentFragment represents a minimal DOM that has no parent. It's used a a lightweight version of the Document that stores a segment of a document structure comprised of nodes just like a standard document.
The key difference is that becuase the document fragment isn't part of the active document tree structure, changes made to the fragment doesn't affect the document, cause reflow, or incur any performance impact that can occur when changes are made.

The browser is constantly working to make the screen match the DOM. When adding a new element, the browser has to run through a reflow calculation (to determine the new screen layout) and then repaint the screen. This takes time.

If a new paragraph is added to the body element, the code would be slower because the growser would have to go through the reflow and repaint process for EACH element paragraph. The browser should only need to do this once, so we need to attach each new paragraph to something other than the active DOM. In previous examples, we've used:
const newDiv = document.createElement('div');
This causes an unnecessary div element to the DOM.

Instead of creating a new Element, we can use the .createDocumentFragment() method to create an empty DocumentFragment object. This is similar to .createElement().

Using the example from before:


    const customDiv = document.createElement('div');            
    //holds the new 'p' elements                                
    for (let i = 1; i <= 200; i++) {                            
        const newElement.creatElement('p');                     
        newElement.innerText = "This is paragraph number " + i; 
                                                                
        customDiv.appendChild(newElement);                      
    }                                                           
    document.body.appendChild(customDiv);                       

Rewritten with .createDocumentFragment() to avoid creating an unnecessary element:


    const fragment = document.createDocumentFragment();         
                                                                
    for (let i = 1; i <= 200; i++) {                            
        const newElement.creatElement('p');                     
        newElement.innerText = "This is paragraph number " + i; 

        fragment.appendChild(newElement);                       
    }                                                           
    document.body.appendChild(fragment);                        

Reflow & Repaint

Reflow - The process of the browser laying out the page.
It happens when the DOM is first displayed (generally after the DOM and CSS have loaded), and again every time something could change the layout. This is a fairly slow process.

Repaint - Happens after reflow as the browser draws the new layout to the screen. Although fairly quick, this should still be limited to how often it happens.

Implications of Single Threading

JavaScript is single-threaded - the processing of one command at a time

JavaScript doesn't execute multiple lines of code or functions at the same tiem.

The Call Stack

The JavaScript enginge keeps a call stack> (basically a list) of the functions that are running. When a function is inkoked, it is added to the list. When all of the code inside a function has been run, the function is removed from the call stack. With the call stack, a function doesn't have to complete before another function is added to the call stack.

Understanding Code Synchronicity

synchronous - existing or occuring at the same time

Some code is not synchronous - meaning the code is written just like any other code, but it's executed at some later point in time. An example of this is event listeners.

The Event Loop

When using event listeners, JavaScript doesn't add these events to the Call Stack. This is because of the JavaScript Event Loop

The simplest explaination of JavaScript's concurrency model uses two rules: if some JavaScript is running, let it run until it's finished ("run-to-completion). If no JavaScript is running, run any pending event handlers.

Since most JavaScript is run in response to an event, this is known as an event loop: Pick up the next event > run its handler > repeat.

There are three parts to consider when thinking about the event loop:

  • The Call Stack
  • Web APIs/the browser
  • An Event Queue
JavaScript event loop

Not all the code that is writen is 100% JavaScript. Some of the code is interacting with the Web APIs (or "browser APIs"). There are many more examples, but both .addEventListener() and setTimeout() are Web APIs.

Important: The key things to remember are:
1. Current synchronous code runs to completion
2. Events are processed when the browser isn't busy. Asynchronous code (such as loading an image) runs outside of this loop and sends an event when it's done.

setTimeout - Running Code Later

Similar to .addEventListener() code being run at some later point, there is the setTimeout() function that will run code at a point later in time. The setTimeout() function takes:

  • a function to run at some later time
  • the number of milliseconds the code should wait before running the function

Example:


setTimeout(function sayHi(){    
    console.log('Hello');       
}, 1000);                       

This would would take 1,000 milliseconds to run, or 1 second.

Since setTimeout()> is an API provided by the browser, the call to setTimeout() gives the sayHi() function over to the browser which it starts a timer. After the timer is finished, the sayHi() function moves to the Queue. If the Call Stack is empty, then the sayHi() function is moved to the Call Stack and executed.