Sign Up

Sign Up to our social questions and Answers Engine to ask questions, answer people’s questions, and connect with other people.

Have an account? Sign In

Have an account? Sign In Now

Sign In

Login to our social questions & Answers Engine to ask questions answer people’s questions & connect with other people.

Sign Up Here

Forgot Password?

Don't have account, Sign Up Here

Forgot Password

Lost your password? Please enter your email address. You will receive a link and will create a new password via email.

Have an account? Sign In Now

Sorry, you do not have permission to ask a question, You must login to ask a question.

Forgot Password?

Need An Account, Sign Up Here

Please type your username.

Please type your E-Mail.

Please choose an appropriate title for the post.

Please choose the appropriate section so your post can be easily searched.

Please choose suitable Keywords Ex: post, video.

Browse

Need An Account, Sign Up Here

Please briefly explain why you feel this question should be reported.

Please briefly explain why you feel this answer should be reported.

Please briefly explain why you feel this user should be reported.

Sign InSign Up

Querify Question Shop: Explore Expert Solutions and Unique Q&A Merchandise

Querify Question Shop: Explore Expert Solutions and Unique Q&A Merchandise Logo Querify Question Shop: Explore Expert Solutions and Unique Q&A Merchandise Logo

Querify Question Shop: Explore Expert Solutions and Unique Q&A Merchandise Navigation

  • Home
  • About Us
  • Contact Us
Search
Ask A Question

Mobile menu

Close
Ask a Question
  • Home
  • About Us
  • Contact Us
Home/ Questions/Q 286

Querify Question Shop: Explore Expert Solutions and Unique Q&A Merchandise Latest Questions

Author
  • 62k
Author
Asked: November 25, 20242024-11-25T09:29:10+00:00 2024-11-25T09:29:10+00:00

Dom Diffing Algorithm Implementation In Vanilla JavaScript

  • 62k

If you have used any SPA framework like React or Vue then you might be familiar with the term “Virtual DOM”. Whenever route path or state changes in react instead of rendering the whole page react only renders the changes, and to accomplish this react uses a DOM diffing algorithm in which it compares the virtual DOM to the actual DOM. In this article I'm going to explain virtual DOM and show you how you can implement the DOM diffing algorithm.

What is DOM

DOM stands for document object model. Its a structured representation of HTML elements of a web page, where each element is a node object which contains some methods and properties.
You can write
console.log(document.body.children);
on a html document and inspect what node has which properties and methods

What is Virtual DOM?

The Virtual DOM is virtual representation of a UI that is kept in JavaScript memory.The virtual DOM is comparable to a blueprint. You make changes in virtual DOM then compare it to the actual DOM and apply the changes thus not re-rendering the whole UI .
Because all the comparison happens in JavaScript memory, its pretty fast.

Implementation

Before we go to the actual coding we should know about node,node types and childNodes.
Starting with node, this are object that represent html elements.
There are many types of node. To know the node type of an element you just have to
console.log(document.getElementById('#node').nodeType)

For this project we just have to know about three main types 1,3 and 8

  • nodeTye 1 means its a html tag
  • nodeType 3 means its a text node
  • nopdeType 8 means its a comment

ChildNodes is an array of children of a node.
you can get the childNodes of an element by simply doing
console.log(document.getElementById('#node').childNodes).
But wait, you can do the same by writing console.log(document.getElementById('#node').children), So, whats the difference?
Both are pretty much same except you will not get text and comment nodes in node.children method.

Let' create a function which will return the tag name if the node is type of a tag ,otherwise the nodeType of the node

    getnodeType(node) {         if(node.nodeType==1) return node.tagName.toLowerCase();         else return node.nodeType;     }; 
Enter fullscreen mode Exit fullscreen mode

Now lets get started by creating an html div with some children element.

    <div id="node">         <h1>Hello</h1>         <p>This is a page</p>     </div> 
Enter fullscreen mode Exit fullscreen mode

This node element has two childNodes h1, p and each of them having a text node as child Node

Now if we want it to replace the childNodes of <div id="node"></div> with

    <h1 style="color:green">Hello</h1>     <p>This is a page</p>     <p>some more text</p> 
Enter fullscreen mode Exit fullscreen mode

we just need to add style attribute to h1 tag and append <p>some more text</p>.
lets check how can we perform this using DOM diffing algorithm.
First , we need a function that converts our template to actual html elements.
We can use inbuild DOMParser() class to achieve that.

function parseHTML(str) {     let parser=new DOMParser();     let doc = parser.parseFromString(str, 'text/html');     return doc.body;   } var vdom=parseHTML(`     <h1 style="color:green">Hello</h1>     <p>This is a page</p>     <p>some more text</p> `) 
Enter fullscreen mode Exit fullscreen mode

if you do console.log(vdom) you can see it returns a body element containing our template as html.

vdom

But if you write console.log(vdom.childNodes) it returns

nodeList

you can see it contains some unnecessary text nodes which contains only new lines and spaces , which might cause problems while diffing , so we need to remove that.
Here's how we can remove this.

function clean(node) {   for(var n = 0; n < node.childNodes.length; n ++)   {     var child = node.childNodes[n];     if     (       child.nodeType === 8        ||        (child.nodeType === 3 && !/S/.test(child.nodeValue) && child.nodeValue.includes('
'))     )     {       node.removeChild(child);       n --;     }     else if(child.nodeType === 1)     {       clean(child);     }   } } 
Enter fullscreen mode Exit fullscreen mode

Basically we are removing All textNodes that contains only new lines. You can read more about it from
Here.

Now combining this with our parseHTML function

function parseHTML(str) {     let parser=new DOMParser();     let doc = parser.parseFromString(str, 'text/html');     clean(doc.body);     return doc.body;   } 
Enter fullscreen mode Exit fullscreen mode

So this is our VDOM.

vdom

And

var dom=document.getElementById('node'); 
Enter fullscreen mode Exit fullscreen mode

this is our dom.

dom

Ok , we can now write our actual diffing function

function diff(vdom,dom){     //if dom has no childs then append the childs from vdom     if (dom.hasChildNodes() == false && vdom.hasChildNodes()==true) {            for (var i = 0; i < vdom.childNodes.length; i++) {            //appending the clone node                dom.append(vdom.childNodes[i].cloneNode(true));             }        } } 
Enter fullscreen mode Exit fullscreen mode

Firstly we check if the DOM element have any child nodes, if it doesn't the we append all childNodes from VDOM to our DOM. While moving childNodes from VDOM to DOM we need to make sure we make clone of the node as we don't want to delete it from our VDOM.

else{ //if both nodes are equal then no need to compare farther if(vdom.isEqualNode(dom)) return;     //if dom has extra child     if (dom.childNodes.length > vdom.childNodes.length) {         let count = dom.childNodes.length -vdom.childNodes.length;         if (count > 0) {             for (; count > 0; count--) {                 dom.childNodes[dom.childNodes.length  - count].remove();                 }             }         }     } 
Enter fullscreen mode Exit fullscreen mode

if both nodes are equal then we stop else we check if DOM has extra nodes then we remove extra nodes from the bottom.

Now we compare all childNodes of VDOM and DOM

//now comparing all childs    for (var i = 0; i < vdom.childNodes.length; i++) {         //if the node is not present in dom append it          if(dom.childNodes[i]==undefined){              dom.append(vdom.childNodes[i].cloneNode(true));           }        } 
Enter fullscreen mode Exit fullscreen mode

if the node is missing from DOM the we append it to the DOM.

        //now comparing all childs     for (var i = 0; i < vdom.childNodes.length; i++) {         //if the node is not present in dom append it         if(dom.childNodes[i]==undefined){              dom.append(vdom.childNodes[i].cloneNode(true));          }else if(getnodeType(vdom.childNodes[i]) == getnodeType(dom.childNodes[i])){          //if same node type           }else{          //else replace             dom.childNodes[i].replaceWith(vdom.childNodes[i].cloneNode(true));            }         } 
Enter fullscreen mode Exit fullscreen mode

else we check if both node have same nodeType ,if not then we replace it with the VDOM child node.

//if same node type //if the nodeType is text if (vdom.childNodes[i].nodeType == '3') { //we check if the text content is same     if (vdom.childNodes[i].textContent != dom.childNodes[i].textContent) {     //replace the text content     dom.childNodes[i].textContent = vdom.childNodes[i].textContent;      }   }else{ patchAttributes(vdom.childNodes[i], dom.childNodes[i])  } 
Enter fullscreen mode Exit fullscreen mode

Now for same node type , if its a text node then we check if textContent of both node are same , if not then we replace it with VDOM child node's textContent.

if its not a text node then we just need to update the attributes .
for this first we have to index the attributes of both virtual and actual node

function attrbutesIndex(el) {         var attributes = {};         if (el.attributes == undefined) return attributes;         for (var i = 0, atts = el.attributes, n = atts.length; i < n; i++) {             attributes[atts[i].name] = atts[i].value;         }         return attributes;     } 
Enter fullscreen mode Exit fullscreen mode

which will return an object with all attributes of a node ,like

{id: 'someid', style: 'color:green'}

now for patching attributes

function patchAttributes(vdom, dom) {         let vdomAttributes = attrbutesIndex(vdom);         let domAttributes = attrbutesIndex(dom);         if(vdomAttributes==domAttributes) return;         Object.keys(vdomAttributes).forEach((key,i) => {             //if the attribute is not present in dom then add it             if(!dom.getAttribute(key)){                 dom.setAttribute(key,vdomAttributes[key]);             }//if the atrtribute is present than compare it             else if(dom.getAttribute(key)){                 if(vdomAttributes[key]!=domAttributes[key]){                     dom.setAttribute(key,vdomAttributes[key]);                 }               }           });         Object.keys(domAttributes).forEach((key,i) => {             //if the attribute is not present in vdom than remove it             if(!vdom.getAttribute(key)){                 dom.removeAttribute(key);             }         });     } 
Enter fullscreen mode Exit fullscreen mode

if the attribute not present in the DOM child node we simply add it and if the attribute is present we update it,but if the attribute is not present in VDOM child node we remove it.
and we repeat the process for each child node;
that's pretty much it.
Lets put it all together

function getnodeType(node) {         if(node.nodeType==1) return node.tagName.toLowerCase();         else return node.nodeType;     }; function clean(node) {     for (var n = 0; n < node.childNodes.length; n++) {         var child = node.childNodes[n];         if (             child.nodeType === 8 ||             (child.nodeType === 3 && !/S/.test(child.nodeValue) && child.nodeValue.includes('
'))         ) {             node.removeChild(child);             n--;         } else if (child.nodeType === 1) {             clean(child);         }     } }  function parseHTML(str) {     let parser = new DOMParser();     let doc = parser.parseFromString(str, 'text/html');     clean(doc.body);     return doc.body; }  function attrbutesIndex(el) {     var attributes = {};     if (el.attributes == undefined) return attributes;     for (var i = 0, atts = el.attributes, n = atts.length; i < n; i++) {         attributes[atts[i].name] = atts[i].value;     }     return attributes; } function patchAttributes(vdom, dom) {     let vdomAttributes = attrbutesIndex(vdom);     let domAttributes = attrbutesIndex(dom);     if (vdomAttributes == domAttributes) return;     Object.keys(vdomAttributes).forEach((key, i) => {         //if the attribute is not present in dom then add it         if (!dom.getAttribute(key)) {             dom.setAttribute(key, vdomAttributes[key]);         } //if the atrtribute is present than compare it         else if (dom.getAttribute(key)) {             if (vdomAttributes[key] != domAttributes[key]) {                 dom.setAttribute(key, vdomAttributes[key]);             }         }     });     Object.keys(domAttributes).forEach((key, i) => {         //if the attribute is not present in vdom than remove it         if (!vdom.getAttribute(key)) {             dom.removeAttribute(key);         }     }); }  function diff(vdom, dom) {     //if dom has no childs then append the childs from vdom     if (dom.hasChildNodes() == false && vdom.hasChildNodes() == true) {         for (var i = 0; i < vdom.childNodes.length; i++) {             //appending             dom.append(vdom.childNodes[i].cloneNode(true));         }     } else { //if both nodes are equal then no need to compare farther if(vdom.isEqualNode(dom)) return;         //if dom has extra child         if (dom.childNodes.length > vdom.childNodes.length) {             let count = dom.childNodes.length - vdom.childNodes.length;             if (count > 0) {                 for (; count > 0; count--) {                     dom.childNodes[dom.childNodes.length - count].remove();                 }             }         }         //now comparing all childs         for (var i = 0; i < vdom.childNodes.length; i++) {             //if the node is not present in dom append it             if (dom.childNodes[i] == undefined) {                 dom.append(vdom.childNodes[i].cloneNode(true));                 // console.log("appenidng",vdom.childNodes[i])             } else if (getnodeType(vdom.childNodes[i]) == getnodeType(dom.childNodes[i])) {                 //if same node type                 //if the nodeType is text                 if (vdom.childNodes[i].nodeType == 3) {                     //we check if the text content is not same                     if (vdom.childNodes[i].textContent != dom.childNodes[i].textContent) {                         //replace the text content                         dom.childNodes[i].textContent = vdom.childNodes[i].textContent;                     }                  }else {                         patchAttributes(vdom.childNodes[i], dom.childNodes[i])                     }             } else {                 //replace                 dom.childNodes[i].replaceWith(vdom.childNodes[i].cloneNode(true));             }             if(vdom.childNodes[i].nodeType != 3){                 diff(vdom.childNodes[i], dom.childNodes[i])             }         }     } } let vdom = parseHTML(`     <h1 style="color:green">Hello</h1>     <p>This is a page</p>     <p>some more text</p>`); let dom = document.getElementById('node'); clean(dom); console.log(vdom) document.querySelector('button').addEventListener('click',function(){      diff(vdom,dom); }) 
Enter fullscreen mode Exit fullscreen mode

The result

The final result

IF you are interested in this type of posts please check out my
blog

Read Next Adding keys to our DOM diffing algorithm

javascriptreacttutorialwebdev
  • 0 0 Answers
  • 0 Views
  • 0 Followers
  • 0
Share
  • Facebook
  • Report

Leave an answer
Cancel reply

You must login to add an answer.

Forgot Password?

Need An Account, Sign Up Here

Sidebar

Ask A Question

Stats

  • Questions 4k
  • Answers 0
  • Best Answers 0
  • Users 1k
  • Popular
  • Answers
  • Author

    How to ensure that all the routes on my Symfony ...

    • 0 Answers
  • Author

    Insights into Forms in Flask

    • 0 Answers
  • Author

    Kick Start Your Next Project With Holo Theme

    • 0 Answers

Top Members

Samantha Carter

Samantha Carter

  • 0 Questions
  • 20 Points
Begginer
Ella Lewis

Ella Lewis

  • 0 Questions
  • 20 Points
Begginer
Isaac Anderson

Isaac Anderson

  • 0 Questions
  • 20 Points
Begginer

Explore

  • Home
  • Add group
  • Groups page
  • Communities
  • Questions
    • New Questions
    • Trending Questions
    • Must read Questions
    • Hot Questions
  • Polls
  • Tags
  • Badges
  • Users
  • Help

Footer

Querify Question Shop: Explore Expert Solutions and Unique Q&A Merchandise

Querify Question Shop: Explore, ask, and connect. Join our vibrant Q&A community today!

About Us

  • About Us
  • Contact Us
  • All Users

Legal Stuff

  • Terms of Use
  • Privacy Policy
  • Cookie Policy

Help

  • Knowledge Base
  • Support

Follow

© 2022 Querify Question. All Rights Reserved

Insert/edit link

Enter the destination URL

Or link to existing content

    No search term specified. Showing recent items. Search or use up and down arrow keys to select an item.