Challenges
DOM

1. Show the different ways of selecting an element from DOM

  • The element can be selected using its tagname, id, attribute, value etc
document.getElementById("elementId"); // elementId is id of the element
document.querySelector(".my-class"); // element having a class 'my-class'

Notes

Above selectors are used to select a first matching element reference even if multiple matches be there

References


2. Show the ways to loop over the Nodelist obtained after querying for the elements

  • The Nodelist can be obtained by queryng for multiple elements
  • As list of elements are array like object (not pure JS array), all the array methods can't be directly used
// Lets assume DOM elements are fetched in the variable domElements
for (let element of domElements) {
  // perform operation on element
}

References


3. Design and Implement a Node Store, which supports DOM element as key

  • Implement it without using inbuilt Map
  • Can you do it in O(1) Time complexity?
class NodeStore {
  constructor() {
    this.store = {};
  }
  /**
   * @param {Node} node
   * @param {any} value
   */
  set(node, value) {
    node.__nodeIdentifier__ = Symbol();
    this.store[node.__nodeIdentifier__] = value;
  }
  /**
   * @param {Node} node
   * @return {any}
   */
  get(node) {
    return this.store[node.__nodeIdentifier__];
  }
 
  /**
   * @param {Node} node
   * @return {Boolean}
   */
  has(node) {
    return !!this.store[node.__nodeIdentifier__];
  }
}

References


4. Implement a function to find the closest ancestor with the provided selector (Element.closest() method)

  • The closest() method traverses the Element and its parents (heading toward the document root) until it finds a node that matches the provided selector string. Will return itself or the matching ancestor. If no such element exists, it returns null.
Element.prototype.closest = function (selector) {
  var el = this;
  while (el) {
    if (el.matches(selector)) {
      return el;
    }
    el = el.parentElement;
  }
  return null;
};

References


5. Write a function to find the corresponding node in two identical DOM trees

  • Given two same DOM tree A, B, and an Element a in A, find the corresponding Element b in B. By corresponding, we mean a and b have the same relative position to their DOM tree root.
const A = document.createElement("div");
A.innerHTML = `
<div>
<div>
  <div>
    <div id="node1"></div>
  </div>
  <div>
  </div>
  <div>
    <div>
      <p id="node2"></p>
    </div>
  </div>
<div>
</div>`;
 
const B = A.cloneNode(true);
const node1 = A.querySelector("#node1");
const node2 = A.querySelector("#node2");
const node1Target = B.querySelector("#node1");
const node2Target = B.querySelector("#node2");
 
findCorrespondingNode(A, B, node1); // node1Target
findCorrespondingNode(A, B, node2); // node2Target
const findCorrespondingNode = (rootA, rootB, target) => {
  if (rootA === target) return rootB;
 
  if (rootA.childElementCount) {
    for (let i = 0; i < rootA.childElementCount; i++) {
      let result = findCorrespondingNode(
        rootA.children[i],
        rootB.children[i],
        target
      );
      if (result) {
        return result;
      }
    }
  }
};

References


6. Write a function to get depth of a given DOM tree

  • A depth of a given DOM tree is the max depth till which DOM nodes are nested
/**
 * @param {HTMLElement | null} tree
 * @return {number}
 */
function getHeight(root) {
  if (!root) return 0;
 
  let maxDepth = 0;
 
  const helper = (current, depth = 1) => {
    if (current.hasChildNodes()) {
      for (let child of current.children) {
        helper(child, depth + 1);
      }
    }
    maxDepth = Math.max(maxDepth, depth);
  };
 
  helper(root);
  return maxDepth;
}

7. Implement a function to get the root node of a given DOM fragment (document.getRootNode() method)

  • Root node is the topmost parent node of any given DOM fragment
/**
 * @param {HTMLElement | null} tree
 * @return {HTMLElement | null}
 */
function getRootNode(tree) {
  if (!tree) return null;
 
  while (tree.parentElement) {
    tree = tree.parentElement;
  }
 
  return tree;
}

References


8. Implement a function to get unique tag names in a given DOM tree

/**
 * @param {HTMLElement | null} tree
 * @return {Array}
 */
function getUniqueTags(root, result = new Set()) {
  if (!root) return [];
 
  if (!result.has(root.tagName)) {
    result.add(root.tagName);
  }
 
  if (root.hasChildNodes()) {
    for (let child of root.children) {
      getUniqueTags(child, result);
    }
  }
 
  return [...result];
}

References


9. Implement a function to get elements by tag name (document.getElementsByTagName() method)

  • The getElementsByTagName method of Document interface returns an HTMLCollection of elements with the given tag name.
  • For example, document.getElementsByTagName('div') returns a collection of all div elements in the document.
/**
 * @param {HTMLElement | null} tree
 * @return {Array}
 */
function getElementsByTagName(root, tagName) {
  if (!root) return [];
 
  let result = [];
 
  if (root.tagName.toLowerCase() === tagName.toLowerCase()) {
    result.push(root);
  }
 
  if (root.hasChildNodes()) {
    for (let child of root.children) {
      result = result.concat(getElementsByTagName(child, tagName));
    }
  }
 
  return result;
}

References


10. Implement a function to check if a given DOM tree has duplicate IDs

  • In a given DOM tree, the id on each node has be unique
  • Although HTML is very forgiving, but we should avoid duplicate identifiers
/**
 * @param {HTMLElement | null} tree
 * @return {Boolean}
 */
function hasDuplicateId(tree, idSet = new Set()) {
  if (!tree) return false;
 
  if (idSet.has(tree.id)) return true;
 
  tree.id && idSet.add(tree.id);
 
  if (tree.hasChildNodes()) {
    for (let child of tree.children) {
      const result = hasDuplicateId(child, idSet);
      if (result) return true;
    }
  }
 
  return false;
}

11. Implement a function to get all descendants of a given DOM node

  • This function retrieves all child elements, regardless of depth.
/**
 * @param {HTMLElement | null} tree
 * @return {Array}
 */
function getAllDescendants(root) {
  if (!root) return [];
 
  let descendants = [];
 
  const helper = (node) => {
    if (node) {
      descendants.push(node);
      if (node.children.length) {
        for (let child of node.children) {
          helper(child);
        }
      }
    }
  };
 
  helper(root);
  return descendants;
}

References


12. Implement a function to clone a DOM tree

  • This function creates a deep copy of the given DOM node and its descendants.
/**
 * @param {HTMLElement | null} tree
 * @return {HTMLElement | null}
 */
function cloneDOMTree(root) {
  return root ? root.cloneNode(true) : null;
}
 

References


13. Implement a function to count all elements in a DOM tree

  • This function counts all child elements, regardless of depth.
/**
 * Counts all child elements in a DOM tree.
 *
 * @param {HTMLElement | null} root - The root element from which to start counting.
 * @return {number} - The total number of child elements.
 */
function countElements(root) {
  if (!root) return 0;
 
  let count = 1; 
 
  for (let child of root.children) {
    count += countElements(child);
  }
  return count;
}

14. Implement a function to serialize a DOM tree into a string

  • This function converts a DOM tree into a string representation.
/**
 * @param {HTMLElement | null} tree
 * @return {string}
 */
function serializeDOM(root) {
  if (!root) return '';
 
  let str = `<${root.tagName.toLowerCase()}`;
 
  for (let attr of root.attributes) {
    str += ` ${attr.name}="${attr.value}"`;
  }
 
  str += '>';
 
  if (root.hasChildNodes()) {
    for (let child of root.children) {
      str += serializeDOM(child);
    }
  }
 
  str += `</${root.tagName.toLowerCase()}>`;
  return str;
}

References


15. What is event delegation in the DOM and how can it be implemented in JavaScript?

  • Event delegation allows you to attach a single event listener to a parent element to manage events from its child elements, leveraging event bubbling. This improves performance and simplifies handling dynamically added elements.

Here’s an example of how event delegation works:

document.querySelector('#parent').addEventListener('click', function(event) {
  if (event.target && event.target.matches('button')) {
    console.log('Button clicked:', event.target.textContent);
  }
});

This approach optimizes performance and reduces memory usage, as fewer event listeners are attached to individual elements.

References


16. How can you find all leaf nodes in a given DOM tree?

  • Leaf nodes in a DOM tree are elements that do not have any child elements. You can traverse the tree recursively to collect all leaf nodes.

Implementation:

function getLeafNodes(element) {
  let leafNodes = [];
 
  // Base case: if the element has no children, it's a leaf node
  if (!element.children || element.children.length === 0) {
    leafNodes.push(element);
    return leafNodes;
  }
 
  // Recursive case: traverse through each child
  for (let i = 0; i < element.children.length; i++) {
    leafNodes = leafNodes.concat(getLeafNodes(element.children[i]));
  }
 
  return leafNodes;
}
 
// Example usage:
const leafNodes = getLeafNodes(document.body);
console.log('Leaf nodes:', leafNodes);
 

In this implementation:

The function checks if the current element has no children. If so, it adds it to the leafNodes array. Otherwise, it recursively collects leaf nodes from each child.

References