递归未声明全局变量时无法正常工作

7
为什么A版本能够工作而B版本不能?我如何在不声明函数外全局变量(这是不好的实践)的情况下使B版本工作呢?我不清楚为什么不能在函数内部声明计数器。
  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;
    }
  };

3
就目前而言,你的问题与“按引用传递”没有任何关系,而在 JavaScript 中也无法使用此功能。 - Pointy
你为什么不使用 element.querySelectorAll('div').length > 5 呢? - user663031
5个回答

7
您真正需要的是两个函数,一个在另一个内部:
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);
}

在这种设置中,您传递一个元素引用,然后外部函数在初始化计数器后调用内部函数。

原始回答不太好

您的第二个版本(“B”)将“count”作为函数的局部变量。每次调用函数都会得到它自己的“count”变量,并且在每次调用中,第一件事情是将其初始化为零。

如果您不想使用全局变量,则可以使用闭包:

 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;
      }
    };
  }();

这段代码将您的实际计数器函数包装在一个包含“count”变量的匿名函数中。它不会是全局的;它将完全私有于“containsFiveOrMoreDivs”函数。这就像最好的两个世界:您可以将“count”视为全局变量,但它并不是全局变量。您也不需要担心携带参数。


3

Javascript中的变量存在函数作用域。在您的版本B中每次调用containsFiveOrMoreDivs时,count总是为0。因此会发生无限递归。

但您可以在每次从函数内部调用时传入'count',并使用它(确保第一次正确初始化):

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;
  }
};

只需像当前一样调用它 (containsFiveOrMoreDivs('元素名称');)


刚在 about:blank 页面上添加了 5 个 div 后,在控制台测试了一下,结果发现它不起作用,返回 false。 - devdropper87
好的观点 - 如果div嵌套在彼此下面,它将起作用,但不会计算兄弟节点!这个问题也适用于@FullStack在下面提供的解决方案。@Pointy - 您的解决方案看起来非常优雅,但是每次在返回的函数内调用containsFiveOrMoreDivs(children[i])时,您都会返回一个新的闭包,该闭包具有值为0的新计数变量。 - oflahero

2

版本B不能正常工作,因为每次调用函数时,counter都会被重新声明,所以counter从未增加。


谢谢,那很有道理!一个建议是将计数作为参数。如果我这样做,那么我怎么在函数内部处理它? - devdropper87
1
请参考下面@fullstacks的答案,关于为计数参数声明默认值。如果您想避免声明任何外部变量,那将是最好的解决方案。 - Pan Wangperawong

2

您的递归函数需要将计数作为参数传入。您现在的方式会无论递归多少次都将计数初始化为0。

下面是一个消耗“执行某事的次数”作为参数的递归函数示例。请修改它以支持您的情况。您的基本情况可能是“计数大于5”,每次递归调用时,您提供给递归调用的计数加1。

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);

如果我将count声明为参数,那么我如何在函数体内处理它? - devdropper87

2

您可以定义一个带有count参数的函数,并传递一个初始值,或者如果您使用的是ECMA 16,则可以通过执行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);

1
这正是我要告诉@freezycold的。很棒的解决方案。 - Pan Wangperawong
这很棒,它将在Firefox中运行,但不会在其他现有的浏览器中运行。 - Pointy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接