As per MDN,
The Node.contains() method returns a Boolean value indicating whether a node is a descendant of a given node, i.e. the node itself, one of its direct children (childNodes), one of the children's direct children, and so on.
But wait, Node.prototype.contains(...) already exists. I want another name for our custom function. Let's google synonym of contains coz
Intense googling later......
Certainly we ain't going with swallow. I think includes would be cool as both Array and String have includes as well in their prototypes.
Before we proceed one important thing to know is that when adding new method to prototype and expecting to use it like so :-
document.includes(document.body),
the method should not be an arrow function so that document can be accessed inside the includes function via this keyword.
Alright then, let's implement Node.prototype.includes in 4 different ways :-
The recursive DFS
1 Node.prototype.includes = function(node){
2 const currentNode = this;
3 if(!currentNode)
4 return false;
5 if(currentNode===node)
6 return true;
7 let isNodeFound = false;
8 for(let index = 0;index<currentNode.childNodes.length;index++){
9 isNodeFound = isNodeFound || currentNode.childNodes[index].includes(node);
10 if(isNodeFound) return true;
11 }
12 return false;
13 }
Explanation :-
- Line 2 to 4 - Set
currentNodetothisand IfcurrentNodedoesn't exist, simply returnfalse. - Line 5 to 6 - if
currentNodeis equal tonodereturntrue. - Line 7 to 13 - Initialize
isNodeFoundtofalse. Then loop overchildNodesof thecurrentNodeand on each child, call theincludesmethod again to check if they include thenodeelement. If they do,isNodeFoundwill ultimately becometruesince it is being Orrrrrrd with the results coming from respectivechildNodesand reassigned to itself. OnceisNodeFoundistrue, we don't need to loop over rest of thechildNodesofcurrentNodeand exit early by returningtrueelse ultimately returnfalse.
The iterative BFS
1 Node.prototype.includes = function (node) {
2 const queue = [];
3 let currentNode = this;
4 queue.push(currentNode);
5 while (queue.length) {
6 currentNode = queue.shift();
7 if (currentNode === node) return true;
8 if (currentNode.hasChildNodes()) {
9 queue.push(...currentNode.childNodes);
10 }
11 }
12 return false;
13 };
Explanation :-
- Line 2 to 4 - Initialize an empty list as
queue. SetcurrentNodetothisandpush(or enqueue to be specific) it. - Line 5 to 12 - While the
queueis not empty, dequeue thecurrentNodefrom front of thequeue(usingshifthere). IfcurrentNodeis equal tonodethen returntrue. Otherwise enqueue thechildNodesofcurrentNode(usingpushhere). Once we are out of thewhileloop, we have traversed all the nodes and can safely say that we couldn't find thenodeand returnfalse.
Note - The above can be transformed to iterative DFS by using pop instead of shift and obviously for the sake of consistency, rename queue to stack.
Till now both the approaches followed the classic DS/Algo traversal with DFS and BFS.
We are now going to see 2 more approaches which take benefit of certain properties that are specifically applicable to DOM nodes.
LCRS (Left Child Right Sibling) form
1 Node.prototype.includes = function (node) {
2 const currentNode = this;
3 if (!currentNode)
4 return false;
5 if (currentNode === node) return true;
6 return !!(currentNode.firstChild?.includes(node) || currentNode.nextSibling?.includes(node))
7 };
Explanation :-
- Line 2 to 5 -
- Initialize
currentNodetothisand ifcurrentNodedoesn't exist, returnfalse. - If
currentNodeis equal tonodereturntrue
- Initialize
- Line 6 - DOM nodes not only have pointers to their childNodes but also to their sibling nodes as well as parent nodes. Here we are going to leverage the sibling factor for easy traversal. So, we can now check whether the current node's
firstChildincludes thenodeOR current node'snextSiblingincludes thenode. Also notice the!!. That's because I have used the?operator due to which we can end up withundefined || undefinedcondition orfalse || undefinedcondition where both evaluate toundefinedwhich is a falsy value and so!!will ensureundefinedcoerces tofalse.
Using parentNode
1 Node.prototype.includes = function(node){
2 const currentNode = this;
3 while(node){
4 if(currentNode===node) return true;
5 node = node.parentNode;
6 }
7 return false;
8 }
Explanation :-
- Line 2 to 7 - Remember DOM node being so attached to it's siblings and parent ? The latter one works well for this use-case as well. While
nodeexists, we check ifcurrentNodeis equal tonodeand if it is we returntrue, else thenodeis made to point to it'sparentNodefor further comparisons. If we exit thewhileloop, it's safe to say that thenodeisn't a contained within thecurrentNodeand thus, returnfalse.
And here is a working codepen with all 4 implementations. Comment the rest for any one to reflect ✨.
Have more ways to implement the same ? Feel free to share your approach in the comment section 👇.








