blog bg

December 04, 2023

JavaScript: Advanced DOM and Events Fundamentals

Share what you learn in this blog to prepare for your interview, create your forever-free profile now, and explore how to monetize your valuable knowledge.

Selecting, Creating, And Deleting Elements

selecting elements

We actually have a special way of selecting the entire document of any webpage and that's .documentElement property.

console.log(document.documentElement);

Output


We can also access the <head> and <body> from the document like shown below,

console.log(document.head);
console.log(document.body);

For these special elements, we don't even need to use any selectors, otherwise as we already know, we can use querySelector(), which will return the first element that matches the CSS selector. Similarly, if we want to select multiple elements for given CSS selector then we will use querySelectorAll(). We can also use other selector methods like getElementById(), getElementsByTagName(), getElementsByClassName() as well.


Creating and Inserting elements

we use createElement() method to create a new DOM element and this method accepts one parameter that is the tag name of the element. For example, 

const divElement = document.createElement('div');

We have created a <div> element, which returned a DOM element that we stored in devElement variable. However, this element is not yet anywhere in our DOM so, if we want it on the page then we need to manually insert it into the page. 

We will use following methods to insert and element into DOM


Using prepend()

The prepend() method adds the element as the first child of the Parent element where we want to insert. For example,  insert the divElement in the <header> section as a first child.

HTML

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
  </head>
  <body>
    <header class="header">
      <div class="first-child"></div>
      <nav class="nav">...</nav>
      <div class="header__title">...</div>
    </header>

    <script>
      const divElement = document.createElement('div');
      divElement.classList.add('first-child')
      
      const header = document.querySelector('header');
      header.prepend(divElement)
    </script>
  </body>
</html>


 

Using append()

The append() method adds the element as the last child of the Parent element where we want to insert. For example, insert the divElement in <header> section as a last child.

HTML

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
  </head>
  <body>
    <header class="header">
      <nav class="nav">...</nav>
      <div class="header__title">...</div>
      <div class="last-child"></div>
    </header>

    <script>
      const divElement = document.createElement('div');
      divElement.classList.add('last-child')
      
      const header = document.querySelector('header');
      header.append(divElement)
    </script>
  </body>
</html>

However, what if wanted to insert multiple copies of the same element? and will it work if add them like shown below?

header.append(divElement)
header.append(divElement)

This will not work ,because a DOM element always only exist at one place at a time so, in that case, first  we actually would have to copy the element using cloneNode() method and then insert it. 

header.append(divElement)
header.append(divElement.cloneNode(true))

We passed true in the cloneNode() method so that, all the child elements of divElement will also be copied

NOTE:- we can also append or prepend multiple elements at the same time like shown below,

header.append(element1, element2, element3);


 

Using before()

The before() method adds the element just before the specified element as a sibling. For example, insert the divElement before the <header> section.

HTML

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
  </head>
  <body>
    <div class="before-header"></div>
    <header class="header">
      <nav class="nav">...</nav>
      <div class="header__title">...</div>
    </header>

    <script>
      const divElement = document.createElement('div');
      divElement.classList.add('before-header')

      const header = document.querySelector('header');
      header.before(divElement)
    </script>
  </body>
</html>

 

Using after()

The after() method adds the element just after the specified element as a sibling. For example, insert the divElement after the <header> section.

HTML

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
  </head>
  <body>
    <header class="header">
      <nav class="nav">...</nav>
      <div class="header__title">...</div>
    </header>
    <div class="after-header"></div>

    <script>
      const divElement = document.createElement('div');
      divElement.classList.add('before-header')

      const header = document.querySelector('header');
      header.after(divElement)
    </script>
  </body>
</html>


 

Removing elements

Using remove()

The remove() method removes the element from the DOM. We will call this method on the element, which we want to remove from the DOM. For example,

HTML

<header class="header">
  <div class="first"></div>
  <div class="second"></div>
  <div class="third"></div>
  <div class="fourth"></div>
</header>

JavaScript

const divElement = document.querySelector('.first');
divElement.remove()

Output

<header class="header">
  <div class="second"></div>
  <div class="third"></div>
  <div class="fourth"></div>
</header>


 

Using removeChild()

The removeChild() method removes a child from the Parent in the DOM and returns the removed Node. To do that, firstly, we need to select the element that we want to remove and get the parent element of the target element using the parentElement property and finally, use the removeChild() method. Let's modify the above example but using removeChild() method. For example,

const divElement = document.querySelector('.first');
divElement.parentElement.removeChild(divElement);


 

Styles, Attributes And Classes

Modifying Styles

The .style property represents the inline styles on the an HTML element. So, we use the style property to add inline style to an element. Generally, the CSS properties are written in kebab-case, but that can't be used in JavaScript, as the dash(-) is used for subtraction. Instead, they will be replaced with camelCase equivalent. For example, we use background-color as backgroundColor in the JavaScript style property,

const divElement = document.querySelector('.first');
divElement.style.backgroundColor = "blue";
divElement.style.textAlign = "justify";
divElement.style.width = "100%";
divElement.style.height = "300px";

NOTE:- We can also read the inline styles of an element using the style property, but we can't get a style that is inside of a class. However, we can still get these styles for a class if we want to, all we need to do is to use the getComputedStyle function. For example,

console.log(getComputedStyle(divElement));

This will give us the object, which contains all of the properties with all of the values and if want to take a certain property then we need to do the following,

console.log(getComputedStyle(divElement).color);

This is the real computed styles, which means that, it's the real style as it appears on the page and even if we don't declare it in our CSS, but the browser will calculate and display it. For example, we want to increase the height of the element by 40px, but we didn't declare it in our CSS.

const divElement = document.querySelector('.first');

const getCurrentHeight = getComputedStyle(divElement).height; //20.34px
divElement.style.height = parseFloat(getCurrentHeight) + 40 + 'px'
console.log(divElement.style.height);//60.3333px

As you can see that, even though we didn't declare the height property in our CSS, the browser calculate in the page and returned "20.34px". We used parseFoat() method to extract the numbers from the "20.34px".

Similarly, we can also change the Custom properties(sometimes referred to as CSS variables) which contains certain values to be reused throughout a document and they are set using the custom property notation, for example, --main-color: black; and they are accessed using the var() function(eg, color: var(--main-color)). These properties are defined in the document root and in JavaScript that is equivalent to the document. They can be accessed using documentElement property. For example, we want to change the color of --main-color property.

document.documentElement.style.setProperty('--main-color', 'orange')

To change the custom CSS property, we use setProperty(), where we will pass the name of the custom property and the value that we want to set it.


 

Modifying Attributes

Attributes are values that contain additional information about HTML elements. They are usually name/value pairs and the most common HTML attributes are src, href, class, id , style and so on. So in JavaScript, we can access and change these attributes using following methods on elements;,


 setAttribute()

The setAttribute() method allows us to add or update the value of an specified attribute of an element. For example, add alt attribute to the <img> tag

HTML

<img src="img/hero.png" class="header__img" />

JavaScript

const img = document.querySelector('.header__img')
img.setAttribute('alt', "Minimalist bank item");

Output

<img src="img/hero.png" class="header__img" alt="Minimalist bank item">


 We could also set the attribute directly on the element. For example,

img.alt = "Minimalist bank items"


 getAttribute()

The getAttribute() method returns the value of a specified attribute and returns null if the attribute not found on that element. For example,

HTML

<img src="img/hero.png" class="header__img" />

JavaScript

const img = document.querySelector('.header__img')
console.log(img.alt);//Minimalist bank items
console.log(img.getAttribute('alt'));//Minimalist bank items

console.log(img.getAttribute('class'));//header__img

As you can see that, we can also read the attribute directly on the element(img.logo).

Let's consider following example,

console.log(img.src);//http://127.0.0.1:8000/img/hero.png
console.log(img.getAttribute('src'));//img/hero.png

As you can see that, when we access the src attribute directly on the element to get the value of src attribute, we get the absolute url of the the image, but with getAttribute() method, we get relative url(relative to the folder, in which the html file is located). This is actually true for the href attribute on <a> tag as well.

We also have the special kind of attributes, that are not part of the HTML standard will be prepended with data- or aria-. For example, let's add one of them to <img> tag and to read it, we use the .dataset property.

HTML

<img src="img/hero.png" class="header__img" data-version-number="3.0">

JavaScript

console.log(img.dataset.versionNumber)//3.0

So these special attributes always stored in the dataset object and to access these properties we just need to transform them into camelCase.


 hasAttribute()

The hasAttribute() method returns true if the element has the specified attribute else returns false. For example,

console.log(img.hasAttribute('src'));//true
console.log(img.hasAttribute('href'));//false


 removeAttribute()

The .removeAttribute() method removes a specified attribute from an element.

img.removeAttribute('src');// src attribute will be removed


 

Modifying Classes

The CSS classes are used to apply styles to multiple elements. Unlike IDs, we can apply a class to multiple elements in a HTML page. In JavaScript, we have .className and .classList properties to work with class attributes.

 

className

The .className property allows us to set or get a class value on an element. For example,

const divElement = document.querySelector('.first');

//get the class on divElement
console.log(divElement.className);//first

//set a className on divElement
divElement.className="second third"

console.log(divElement.className);//second third


 

classList.add()

The classList.add() method allows us to add one or more class values on an element. For example

const divElement = document.querySelector('.first');

divElement.classList.add("second","third")
console.log(divElement.className);//first second third

Unlike in className property, classList.add() will add a new class to the list of existing classes. We can  also add multiple classes as comma-separated strings.

 

classList.remove()

The classList.remove() method allows us to remove one or more class values on an element. For example,

const divElement = document.querySelector('.first');

divElement.classList.add("second","third")
console.log(divElement.className);//first second third

divElement.classList.remove("first","third")
console.log(divElement.className);//second

 

classList.replace()

The classList.replace() method allows us to replace an existing class value with  a new class value in an element. For example

const divElement = document.querySelector('.first');

divElement.classList.add("second","third")
console.log(divElement.className);//first second third

divElement.classList.replace("first","new-first")
console.log(divElement.className);//new-first second third


 classList.contains()

The classList.contains() method returns true if the given class value exists in the element else returns false. For example,

const divElement = document.querySelector('.first');

divElement.classList.add("second","third")
console.log(divElement.className);//first second third

divElement.classList.contains("first")//true
divElement.classList.contains("new-first")//false


 classList.toggle()

The classList.toggle() method allows us to toggle a class on and off in an element. For example,

const divElement = document.querySelector('.first');

divElement.classList.toggle("first")//switch between .first true and false


 

Types of Events and Event Handlers

An event is basically a signal that is generated by a certain DOM node and a signal means that, , for example, a click or mouse moving or webpage is loading. We can list and fold these events on our code using EventListeners. For example, a click event that will happen when  a user clicks.

Here are the list of available different events on MDN

There are two ways of listening for event. 

  • using addEventListener

For example, we will use mouseenter event,

const header = document.querySelector('header');

header.addEventListener('mouseenter', function(e){
    console.log('You are currely on the Header section');
});
  • using the on-event property

Using the on-event property directly on the element. For example, when we want to listen for mouseenter, there is a property called onmouseenter and then we can simply set that property to the event function.

const header = document.querySelector('header');

header.onmouseenter = function(e){
    console.log('You are currely on the Header section');
}

For every event that we see in the events list on MDN, there is one on-event property, for example click event has onclick property. However, nowadays we usually use addEventListener over on-event property, because there are two reasons why addEventListener is better than on-event property,

  • it allows us to add multiple event listeners to the same event. If do the same with on-event property, the second function would simply overwrite the  first one.
  • we can actually remove an event handler in case we don't need it anymore.

 

Event Propagation: Bubbling and Capturing

The JavaScript events have a very important property and it's called capturing phase and a bubbling phase. let's consider following HTML code and a DOM tree, but only for the <a> tag.

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>A Simple Page</title>
  </head>
  <body>
    <section>
      <p>A paragraph with a <a>link</a></p>
      <p>A second parapgraph</p>
    </section>
    <section>
      <img src="dom.png" alt="the dom" />
    </section>
  </body>
</html>

 


 As you can see that, we can see all the parent elements of the <a> tag in the DOM tree, that's because we are going to simulate what exactly happens with an event when someone clicks on that link. When a click happens on the link, the DOM then generates a click event right away. However, this event is actually not generated at the target element(<a> tag). Instead, the event is actually generated at the root of the document(at the very top of the DOM tree). And from there, the so-called capturing phase happens, where the event travels all the way down from the document root to the target element. As the event travels down the tree, it will pass through every single parent element of the target element. In our example, it travels through the <HTML> element, <body> element, <section> element, <p> element and then finally reaches it's target(<a> tag).

As soon as the event reaches the target, the target phase begins, where the events can be handled right at the target and as we know already, we do that with an event listener, which wait for a certain event to happen on a certain element and as soon as the event occurs, it runs the attached callback function. This happens in the target phase. After reaching the target, the event then actually travels all the way up to the document route again, is the so-called bubbling phase. In this phase, just like in the capturing phase, the event passes through all its parent elements. So as an event travels down and up the tree, they pass through all the parent elements, but not through any sibling element.

As the event bubbles through a parent element, it's as if the event had happened right in that very element. What this means is that if we attach the same event listener, for example, to <section> tag, then we would get the same event result that happened in the <a> element.  So, we would have handled the exact same event twice, once at it's target and once at on of its parent element. So, this behaviour will allow us to implement really powerful patterns. By default, events can only be handled in  the target, and in the bubbling phase. For example,

HTML

<nav class="nav">
  <ul class="nav__links">
    <li class="nav__item">
      <a class="nav__link" href="#section--1">Features</a>
    </li>
    <li class="nav__item">
      <a class="nav__link" href="#section--2">Operations</a>
    </li>
    <li class="nav__item">
      <a class="nav__link" href="#section--3">Testimonials</a>
    </li>
    <li class="nav__item">
      <a class="nav__link nav__link--btn btn--show-modal" href="#"
        >Open account</a
      >
    </li>
  </ul>
</nav>

JavaScript

const navLink = document.querySelector('.nav__link')
const navLinkItem = document.querySelector('.nav__item')
const navLinks = document.querySelector('.nav__links')
const nav = document.querySelector('.nav')

navLink.addEventListener('click',function(){
    console.log('Nav link is clicked');
});

navLinkItem.addEventListener('click',function(){
  console.log('Nav Item is clicked');
});


navLinkItem.addEventListener('click',function(){
    console.log('Nav Links is clicked');
});

nav.addEventListener('click',function(){
    console.log('Nav  is clicked');
});

Output


As you can see that, All the events are happening in the target phase and bubble phase.


 

DOM Traversing
 DOM traversing is basically walking through the DOM, which means that we can select an element based on another element, for example, a direct child or a direct parent element or siblings or even sometimes we don't know the structure of the DOM at runtime and in all these cases, we need DOM traversing. Let's consider following HTML code,

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <link rel="shortcut icon" type="image/png" href="img/icon.png" />

    <link
      href="https://fonts.googleapis.com/css?family=Poppins:300,400,500,600&display=swap"
      rel="stylesheet"
    />
    <link rel="stylesheet" href="style.css" />
    <title>Bankist | When Banking meets Minimalist</title>
  </head>
  <body>
     <header class="header">
      <nav class="nav">
        <ul class="nav__links">
          <li class="nav__item">
            <a class="nav__link" href="#section--1">Features</a>
          </li>
        </ul>
      </nav>
      <div class="header__title">
        <h1>
          When
          <!-- Green highlight effect -->
          <span class="highlight">banking</span>
          meets<br />
          <span class="highlight">minimalist</span>
        </h1>
        <h4>A simpler banking experience for a simpler life.</h4>
        <button class="btn--text btn--scroll-to">Learn more &DownArrow;</button>
        <img
          src="img/hero.png"
          class="header__img"
          alt="Minimalist bank items"
        />
      </div>
    </header>

    <section class="section" id="section--3">
    </section>

    <section class="section section--sign-up">
    </section>

    <footer class="footer">
    </footer>
    <script src="script.js"></script>
  </body>
</html>

In this code, we have <h1> element and from there we are gonna go downwards, upwards and also sideways.

let's starts going downwards, it is basically select the child elements. The first way of doing that is to use querySelectorAll(), because it works on the elements. So, we simply do the following,

const h1 = document.querySelector('h1');
console.log(h1.querySelectorAll('highlight'));

As you can see that, h1.querySelectorAll('highlight') will selects all the elements with highlight class that are children of the <h1> element and that would work no matter how deep these child elements would be inside of the <h1> element. However, with this approach, with direct children of the <h1>, it will also bring the children of children as it would go down as deep as necessary into the DOM tree. But, if any elements with highlight class that are outside of the <h1> element, they would not get selected because they were not children of the <h1> element.

Sometimes, all we need are actually direct children of the element and for that we use childNodes property,

console.log(h1.childNodes);

Output

 
As you can see that, we get texts, comments and other child elements and that's because we already know that nodes can be anything like texts or elements or comments. However, generally we interested in the elements themselves so that, we could use .textContent or .innerHTML on them. In that case, childNodes is not that useful but instead we can use .children property,
 
console.log(h1.children);

Output


This gives us an HTML collection, which only gives the three elements that are actually inside of the <h1> and they are direct children, that's because .children works only for direct children. We can also get the first and the last child of an Element using .firstElementChild and .lastElementChild properties. For example, 

 
//get the first child element of the h1
console.log(h1.firstElementChild);

//get the last child element of the h1
console.log(h1.lastElementChild);

Now, let's go upwards, it is basically selecting parents of an element. To get direct parents, we could use h1.parentNode, but this is not that useful as it is similar to h1.childNodes. There's also the .parentElement property, which is usually the one that we are interested in.

console.log(h1.parentElement);

Output

However, most of the time we actually need a parent element that is not a direct parent or in other wards, we might need to find a parent element no matter how far away it is and the DOM tree. For that, we have the .closest() method. For example, we had multiple headers so, multiple elements with  a class of header, but for some reason we only wanted to find the one that is a parent of h1. The closest() method receives a query string just like querySelector(). For example we wanted to find the closest parent of <h1> and change the background color of the parent.

 

const closestParent = h1.closest('.header')
closestParent.style.backgroundColor="green"
console.log(closestParent);

Output

Now, let's go sideways, it is basically selecting siblings and for some reasons in JavaScript we can only access the direct siblings. So, basically only the previous and the next one. 

  • To get the prevous siblings we will use .previousElementSibling property and it will return null if no previous sibling found
const previousSibling = h1.previousElementSibling;
As you can see the HTML code, there is no previous sibling fot <h1>, so it will return null.
 
  • To get the next siblings we will use .nextElementSibling property and it will return null if no next sibling found
const nextSibling = h1.nextElementSibling;

Output

However, if we need all the siblings and not just the previous and next one, we can use the trick of moving up to the parent element and then read all the children from there. For example, find all the siblings of <h1>.

 

const parentElement = h1.parentElement;
const children  = parentElement.children
console.log(children);

Output

It returns an HTML collection and this is not an array, but it is still iterable, that we can spread into an array. By doing this we can create an array and then we can loop over it. For example,
const parentElement = h1.parentElement;
let children  = parentElement.children;
[...children].forEach(element => {
    if(element !== h1) element.style.transform = 'scale(0.5)'
});

So, this is actually the fundamentals of DOM traversing. 
 

 

Passing Arguments To Event Handlers

Consider following HTML code,

<nav class="nav">
  <ul class="nav__links">
    <li class="nav__item">
      <a class="nav__link" href="#section--1">Features</a>
    </li>
    <li class="nav__item">
      <a class="nav__link" href="#section--2">Operations</a>
    </li>
    <li class="nav__item">
      <a class="nav__link" href="#section--3">Testimonials</a>
    </li>
    <li class="nav__item">
      <a class="nav__link nav__link--btn btn--show-modal" href="#"
        >Open account</a
      >
    </li>
  </ul>
</nav>

What we are going to do is that when ever, mouse over an nav link we fade out other nav link items. 

const nav = document.querySelector('.nav')
const handlerHover = function(opacity,e){
    if(e.target.classList.contains('nav__link')){
        const link = e.target;
        const siblings = link.closest('.nav').querySelectorAll('.nav__link')
        siblings.forEach(element => {
            if(element !== link) element.style.opacity = opacity;
        });
    }
}
nav.addEventListener('mouseover', handlerHover.bind(null,0.5));

nav.addEventListener('mouseout', handlerHover.bind(null,1));
We can pass the arguments to the Event handlers using the bind() method
 
However, we have another way of passing arguments, 
const nav = document.querySelector('.nav')
const handlerHover = function(e,opacity){
    console.log(opacity);
    if(e.target.classList.contains('nav__link')){
        const link = e.target;
        const siblings = link.closest('.nav').querySelectorAll('.nav__link')
        siblings.forEach(element => {
            if(element !== link) element.style.opacity = opacity;
        });
    }
}

nav.addEventListener('mouseover', function(e){
    handlerHover(e,0.5);
});

nav.addEventListener('mouseout', function(e){
    handlerHover(e,1);
});

However, I prefer to use bind() method rather than calling the function in the callback function.

 

Lifecycle DOM Elements

DOM lifecycle means that, from the moment that the page is first accessed until the user leaves it. However, there are some events that occur in the DOM during a webpage's life cycleand which are,

DOMContentLoaded event

This event is fired by the document as soon as the HTML is completely parsed, which means that the HTML has been downloaded and been converted to the DOM tree. Also, all scripts must be downloaded and executed before the DOMcontentLoaded event can happen. Since, this happens on the document, we can call the addEventListener method on the document.

document.addEventListener('DOMContentLoaded', function(e){
  console.log('HTML parsed and DOM tree built!');
});

However, this event doesn't actually wait for images and other external resources to load, instead, just HTML and JavaScript need to be loaded. However, when we have the script tag at the end of the body tag, basically it's the last thing that is going to be be read in the HTML so, browser will only find the script when the rest of the HTML is already parsed. So, when we have script tag at the end of the body, then we don't need to listen for the DOMContentLoaded event.

 

load event

The load event fired by the window as soon as not only the HTML is parsed, but also all the images and external resources like CSS files are also loaded. So basically this event gets fired only after the complete page has finished  it's loading. We can also listen that even by calling the addEventListener method on the window.

window.addEventListener('load', function(e){
  console.log('HTML page is fully loaded!');
});

 

beforeunload event

The beforeunload event is fired by the window just before a user is about to leave a page. For example, after clicking the close button in the browser tab, we can acutally use this event to ask users if they are 100% sure that they want to leave the page. 

window.addEventListener('beforeunload', function(e){
  e.preventDefault()
  e.retrunValue = ""
});

In some browsers to make this work, we need to call e.preventDefault() but in browsers like chrome it's not necessary. In order to display a leaving confirmation, we need to set the return value on the event an empty string so that, if we try to close the tab, we will get a pop up, which asks us if we want to leave the site.

 

Efficient Script Loading: defer and async

There are two different ways of loading a JavaScript in HTML, which  are async and defer. These attributes are going to influence the way, in which the JavaScript is fetched, which basically means download and then executed. We usually write the script tag either in the document <head> or at the end of the <body> tag. 

When we include a script without specifying one of these attributes in the <head> tag, following process will happen as show below,

 


 As the user loads the page and receives the HTML, the HTML code will start to be parsed by the browser, which means that, building the DOM tree from the HTML elements. At a certain point, it will find a script, start to fetch the script and then execute it. During this time, the HTML parsing will actually stop and only after that, the rest of the HTML can be parsed. At the end of the parsing, the DOMContentLoaded event will finally get fired. However, this is not ideal as the browser just sitting there doing nothing till the script is downloaded and executed, because this can have a huge impact on the page's performance.

On the other hand, when we include the  script tag at the end of the body,following process will happen as show below, 


 Firstly, the HTML is parsed, then the script tag is found at the end of the document, then the script is fetched and then finally the script gets executed. Compare to adding script tag in the <head>, this process is much better, but this is still not perfect, because the script could have been downloaded before or while the HTML was still being parsed. 

 

Let's try with Async attribute

<script async src="script.js" >

When we include the script with async attribute in the <head> tag, following process will happen as shown below,


 As you can see that, the script is loaded at the same time as the HTML is parsed in an asynchronous way. However, the HTML parsing still stops for the script execution until it finishes it's execution. So, the script is actually downloaded asynchronously, but then it's executed immediately in a synchronous way.

Usually, the DOMContentLoaded event waits for all scripts to execute, except for async scripts to be downloaded and executed. So with async, DOM content loaded is fired off as soon as the HTML finishes parsing.


As you can see that, some async scripts takes more time to fetch them but DOMContentLoaded event doesn't wait for them to finish their process.

Also, async scrips are not guaranteed to be executed in the exact order that they are declared in the code. So in this case, the script arrives first get's executed first.

 

Let's try with defer attribute

<script defer src="script.js" >

When we include the script with defer attribute in the <head> tag, Following process will happen as shown below,


 As you can see that, the script is still loaded asynchronously, but the execution of the script is deferred unitl the end of the HTML parsing. In practice, loading time is similar to the async attribute, but the key difference is that, the HTML parsing is never interrupted, because the script is only executed at the end.

However,  the defer forces the DOMcontentLoaded event to only get fired after the whole script has been downloaded and executed


As you can see that, some defer scripts takes more time to fetch them but still DOMContentLoaded event wait for them to finish their process.

Using the defer attributes guarantees that the scripts are actually executed in the order that they are declared or written in the code.

NOTE:- Whenever, we include the the script in the <body>, fetching and executing the script always happens after parsing the HTML anyway. So in this situations async and defer have no practical effect at all.

However, it is important to note that only modern browsers support async and defer. So, if we need to support all browsers, then we need to put the script tag at the end of the body and not in the head. That's because this is actually not a JavaScript feature, but an HTML5 feature

 

Next ArticleJavaScript: How JavaScript Works Behind the Scenes

450 views

Please Login to create a Question