7

Why does version A work but version B doesn't? How can I make version B work without declaring a global variable outside the function (which is bad practice)? I'm not clear on why I can't just declare count inside the function itself.

A)

  var count = 0;

  var containsFiveOrMoreDivs = function(domElement) {

    if (domElement && domElement.tagName === "DIV") {
      count++;
    }


    //base case: 

    if (count >= 5) {
      return true;
    } else {
      if (domElement.hasChildNodes()) {
        var children = domElement.childNodes;
        for (var i = 0; i < children.length; i++) {

          if (containsFiveOrMoreDivs(children[i])) {
            return true;
          }

        }
      }
      return false;
    }
  };

B)

 var containsFiveOrMoreDivs = function(domElement) {
    var count = 0;
    if (domElement && domElement.tagName === "DIV") {
      count++;
    }


    //base case: 

    if (count >= 5) {
      return true;
    } else {
      if (domElement.hasChildNodes()) {
        var children = domElement.childNodes;
        for (var i = 0; i < children.length; i++) {

          if (containsFiveOrMoreDivs(children[i])) {
            return true;
          }

        }
      }
      return false;
    }
  };
2
  • 3
    For what it's worth, your issue has nothing to do with "pass by reference", which is not possible in JavaScript anyway. Commented Aug 30, 2015 at 1:02
  • Is there some reason you are not doing element.querySelectorAll('div').length > 5? Commented Aug 30, 2015 at 4:33

5 Answers 5

7

What you really need is two functions, one inside the other:

function containsFiveOrMoreDivs(domElement) {
  var count = 0;
  function doCount(domElement) {
      if (domElement && domElement.tagName === "DIV") {
        count++;
      }

      //base case: 

      if (count >= 5) {
        return true;
      }
      else {
        if (domElement.hasChildNodes()) {
          var children = domElement.childNodes;
          for (var i = 0; i < children.length; i++) {

            if (doCount(children[i])) {
              return true;
            }

          }
        }
        return false;
      }
   }
   return doCount(domElement);
}

In that setup, you pass in an element reference, and then the outer function calls the inner function after initializing the counter.


original not very good answer here

Your second version ("B") has "count" as a local variable of the function. Each invocation of the function gets its very own "count" variable, and in each invocation the first thing that happens is that it's initialized to zero.

If you don't want a global, you can use a closure:

 var containsFiveOrMoreDivs = function() {
    var count = 0;
    return function(domElement) {
      if (domElement && domElement.tagName === "DIV") {
        count++;
      }

      //base case: 

      if (count >= 5) {
        return true;
      } else {
        if (domElement.hasChildNodes()) {
          var children = domElement.childNodes;
          for (var i = 0; i < children.length; i++) {

            if (containsFiveOrMoreDivs(children[i])) {
              return true;
            }

          }
        }
        return false;
      }
    };
  }();

That code wraps your actual counter function in an anonymous function that includes the "count" variable. It won't be global; it'll be completely private to the "containsFiveOrMoreDivs" function. This is like the best of both worlds: you get to treat "count" as a global, but it's not global. You don't need to worry about carrying a parameter around either.

Sign up to request clarification or add additional context in comments.

Comments

3

Variables in Javascript exist in function scope. Every time you call containsFiveOrMoreDivs, count will always be 0 in your version B. Hence, infinite recursion.

What you can do, however, is pass in 'count' each time you call from within the function, and use that (ensuring it's initialised correctly the first time):

var containsFiveOrMoreDivs = function(domElement, count) {
  if (!count) {
    count=0;
  }
  if (domElement && domElement.tagName === "DIV") {
    count++;
  }


  //base case: 

  if (count >= 5) {
    return true;
  } else {
    if (domElement.hasChildNodes()) {
      var children = domElement.childNodes;
      for (var i = 0; i < children.length; i++) {

        if (containsFiveOrMoreDivs(children[i], count)) {
          return true;
        }

      }
    }
    return false;
  }
};

Call it just like you currently are (containsFiveOrMoreDivs('elementname');)

2 Comments

that doesn't work, just tested it in the console on about:blank after appending 5 divs! it returns false.
Good point - it will work if the divs are nested underneath each other, but it won't count siblings! This problem also applies to @FullStack 's solution below. @Pointy - your solution looks quite elegant, but each time you call containsFiveOrMoreDivs(children[i]) within the returned function, you are returning a new closure that has a fresh count variable of value 0.
2

Version B will not work because each time the function is called the counter is redeclared, so counter never increments.

2 Comments

Thanks, that makes sense! A suggestion was to make count a parameter. If I do that, how can I handle it inside the function?
See @fullstacks answer below about declaring a default value for the count parameter. That would be the best solution if you want to avoid declaring any external variable.
2

Your recursive function needs to consume count as an argument. The way you have it will initialize count to 0 no matter how many times you recurse.

Here's an example of a recursive function that consumes "the number of times to do something" as a parameter. Modify it to support your case. Your base case would be something like "count is greater than 5", and each time you call recursively, you add 1 to the count you provide to the recursive call.

function executeMany(fn, count) {
    if (count > 0) {
        fn();
        executeMany(fn, count - 1)
    }
}

// this logs "Test" to the console twice
executeMany(function() { console.log("Test"); }, 2);

1 Comment

thanks! if I declare count as a parameter, how do I handle it inside the body of the function?
2

You could define the function with a count parameter and pass an initial value or if you are using a ECMA 16 you could set a default value for the parameter by doing count=0.

var containsFiveOrMoreDivs = function(domElement, count) {
    if (domElement && domElement.tagName === "DIV") {
      count++;
    }


    //base case: 

    if (count >= 5) {
      return true;
    } else {
      if (domElement.hasChildNodes()) {
        var children = domElement.childNodes;
        for (var i = 0; i < children.length; i++) {

          if (containsFiveOrMoreDivs(children[i]), count) {
            return true;
          }

        }
      }
      return false;
    }
  };

// call function and set counter to some initial value, such as zero
containsFiveOrMoreDivs(domElement, 0);

2 Comments

This was exactly what I was going to tell @freezycold. Great solution.
This is great and it will work in Firefox and no other current browsers.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.