【问题标题】:Calling a javascript function recursively递归调用javascript函数
【发布时间】:2011-08-15 12:51:31
【问题描述】:

我可以像这样在变量中创建递归函数:

/* Count down to 0 recursively.
 */
var functionHolder = function (counter) {
    output(counter);
    if (counter > 0) {
        functionHolder(counter-1);
    }
}

这样,functionHolder(3); 将输出 3 2 1 0。假设我做了以下事情:

var copyFunction = functionHolder;

copyFunction(3); 将输出3 2 1 0 如上所述。如果我随后将functionHolder 更改如下:

functionHolder = function(whatever) {
    output("Stop counting!");

然后functionHolder(3); 将提供Stop counting!,正如预期的那样。

copyFunction(3); 现在给出3 Stop counting!,因为它指的是functionHolder,而不是函数(它本身指向)。这在某些情况下可能是可取的,但是有没有办法编写函数以便它调用自身而不是保存它的变量?

也就是说,是否可以更改 functionHolder(counter-1); 行,这样当我们调用 @987654346 时,通过所有这些步骤仍然会得到 3 2 1 0 @?我试过this(counter-1);,但这给了我错误this is not a function

【问题讨论】:

  • NB 在函数内部,this 指的是函数执行的上下文,而不是函数本身。在您的情况下,这可能指向全局窗口对象。

标签: javascript function recursion function-expression


【解决方案1】:

使用命名函数表达式:

你可以给函数表达式取一个实际上是private的名字,并且只在函数内部可见:

var factorial = function myself (n) {
    if (n <= 1) {
        return 1;
    }
    return n * myself(n-1);
}
typeof myself === 'undefined'

这里myself仅在函数内部可见

您可以使用此私有名称递归调用函数。

参见 ECMAScript 5 规范的 13. Function Definition

可以从 FunctionExpression 的 FunctionBody 内部引用 FunctionExpression 中的标识符,以允许函数递归调用自身。但是,与 FunctionDeclaration 不同,FunctionExpression 中的 Identifier 不能被引用,也不会影响包含 FunctionExpression 的范围。

请注意,最高版本 8 的 Internet Explorer 行为不正确,因为名称实际上在封闭变量环境中可见,并且它引用了实际函数的副本(请参阅 patrick dw'在下面发表评论)。

使用 arguments.callee:

您也可以使用arguments.callee 来引用当前函数:

var factorial = function (n) {
    if (n <= 1) {
        return 1;
    }
    return n * arguments.callee(n-1);
}

ECMAScript 第 5 版禁止在 strict mode 中使用 arguments.callee(),但是:

(来自MDN):在普通代码中,arguments.callee 指的是封闭函数。这个用例很弱:只需命名封闭函数!此外,arguments.callee 大大阻碍了像内联函数这样的优化,因为如果访问 arguments.callee,就必须提供对未内联函数的引用。严格模式函数的 arguments.callee 是一个不可删除的属性,在设置或检索时会抛出。

【讨论】:

  • +1 虽然它在 IE8 和更低的版本中有点错误,因为 myself 在封闭变量环境中实际上是可见的,并且它引用了实际 @987654332 的 duplicate @ 功能。不过,您应该可以将外部引用设置为 null
  • 感谢您的回答!两者都有帮助并以两种不同的方式解决了问题。最后我随机决定接受哪个:P
  • 仅供我理解。每次返回时乘以函数的原因是什么? return n * myself(n-1); ?
  • 为什么函数会这样工作? jsfiddle.net/jvL5euho/18 if 循环 4 次后。
  • 根据一些参考参数。被调用者不会在严格模式下工作。
【解决方案2】:

您可以使用arguments.callee [MDN] 访问函数本身:

if (counter>0) {
    arguments.callee(counter-1);
}

但是,这将在严格模式下中断。

【讨论】:

  • 我认为这已被弃用(并且在严格模式下不允许)
  • @Felix:是的,“严格模式”会给出TypeError,但我还没有找到任何正式声明arguments.callee (或任何严格模式违规) i> 在“严格模式”之外不推荐使用。
  • 感谢您的回答!两者都有帮助并以两种不同的方式解决了问题。最后我随机决定接受哪个:P
【解决方案3】:

您可以使用 Y 组合器:(Wikipedia)

// ES5 syntax
var Y = function Y(a) {
  return (function (a) {
    return a(a);
  })(function (b) {
    return a(function (a) {
      return b(b)(a);
    });
  });
};

// ES6 syntax
const Y = a=>(a=>a(a))(b=>a(a=>b(b)(a)));

// If the function accepts more than one parameter:
const Y = a=>(a=>a(a))(b=>a((...a)=>b(b)(...a)));

你可以这样使用它:

// ES5
var fn = Y(function(fn) {
  return function(counter) {
    console.log(counter);
    if (counter > 0) {
      fn(counter - 1);
    }
  }
});

// ES6
const fn = Y(fn => counter => {
  console.log(counter);
  if (counter > 0) {
    fn(counter - 1);
  }
});

【讨论】:

    【解决方案4】:

    我知道这是一个老问题,但如果您想避免使用命名函数表达式,我想我会提供另一种可以使用的解决方案。 (不是说你应该或不应该避免它们,只是提出另一种解决方案)

      var fn = (function() {
        var innerFn = function(counter) {
          console.log(counter);
    
          if(counter > 0) {
            innerFn(counter-1);
          }
        };
    
        return innerFn;
      })();
    
      console.log("running fn");
      fn(3);
    
      var copyFn = fn;
    
      console.log("running copyFn");
      copyFn(3);
    
      fn = function() { console.log("done"); };
    
      console.log("fn after reassignment");
      fn(3);
    
      console.log("copyFn after reassignment of fn");
      copyFn(3);
    

    【讨论】:

      【解决方案5】:

      这是一个非常简单的例子:

      var counter = 0;
      
      function getSlug(tokens) {
          var slug = '';
      
          if (!!tokens.length) {
              slug = tokens.shift();
              slug = slug.toLowerCase();
              slug += getSlug(tokens);
      
              counter += 1;
              console.log('THE SLUG ELEMENT IS: %s, counter is: %s', slug, counter);
          }
      
          return slug;
      }
      
      var mySlug = getSlug(['This', 'Is', 'My', 'Slug']);
      console.log('THE SLUG IS: %s', mySlug);
      

      请注意,counter 相对于 slug 的值“向后”计数。这是因为我们记录这些值的位置,因为函数 recurs 在记录之前 - 所以,我们基本上会越来越深入地嵌套到 call-stack 记录发生之前。

      一旦递归遇到最终调用堆栈项,它蹦床“退出”函数调用,而counter 的第一个增量发生在最后一个嵌套调用内部。 p>

      我知道这不是对提问者代码的“修复”,但考虑到标题,我认为我会概括地举例说明递归,以便更好地理解递归。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-09-21
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多