Search code examples
javascriptrecursiondomcallbacktraversal

What is preventing function from being called infinitely?


The functions purpose is to traverse the DOM passing through a callback function on each child element. If traverseDom calls itself and starts the entire function over, I would expect that element = element.nextElementSibling would never be reached. Even though we would eventually hit the last child in the node tree, I would assume that nothing is stopping the function from calling itself infinitely trying to find additional children even after we’ve reached the last child.

function traverseDom(element, callback) {
  callback(element);
  element = element.firstElementChild;

  while (element) {
    traverseDom(element, callback);
    element = element.nextElementSibling;
  }
}
const subTree = document.getElementById("subTree");
traverseDom(subTree, function(element) {
  console.assert(element !== null, element.nodeName);
});
<div id="subTree">
  <form>
    <input type="text" />
  </form>
  <p>Paragraph</p>
  <span>Span</span>
</div>

I expected this to run infinitely and never reach the sibling element declaration.


Solution

  • At the deepest nested element, element.firstElementChild will be null, and so after assigning that to element, the following condition will not be true:

    while(element) {     
    

    ...and so the loop is not entered at all. There is no further recursion at that point, the function returns, and backtracking can happen. The function executing at the previous level of recursion may still loop further, but eventually there will always be a deepest level where the loop does not execute. These represent the leaves in the depth-first traversal tree.

    Variable Scope

    There is also another aspect which may lead to confusion: the variable element is local to the current function execution context: a change in its value (due to an assignment), will not affect the variable with the same name in the calling function.

    To clarify this, you could also rewrite the code to use a different variable name to which you will assign the child nodes:

    function traverseDom(element, callback) {
      callback(element);
      var child = element.firstElementChild;
    
      while (child) {
        traverseDom(child, callback);
        child = child.nextElementSibling;
      }
    }
    const subTree = document.getElementById("subTree");
    traverseDom(subTree, function(element) {
      console.assert(element !== null, element.nodeName);
    });
    <div id="subTree">
      <form>
        <input type="text" />
      </form>
      <p>Paragraph</p>
      <span>Span</span>
    </div>

    This code will produce the same result; it uses the same logic, except that it does not assign a new value to element, but uses a different variable for that new value (the first child). But notice that when the function is called recursively, the value of child becomes the value of the parameter-variable element.