递归调用javascript函数


90

我可以在变量中创建一个递归函数,如下所示:

/* 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);以便3 2 1 0在调用时仍能完成所有这些步骤copyFunction(3);?我试过了,this(counter-1);但这给了我错误this is not a function


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

Answers:


146

使用命名函数表达式:

您可以为函数表达式指定一个实际上是私有的名称,并且只有在函数内部才能看到:

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

下面myself该函数的可见里面只有自己。

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

请参阅13. Function DefinitionECMAScript 5规范:

可以从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的第五版禁止在严格模式下使用arguments.callee(),但是:

(来自MDN):在常规代码中arguments.callee指的是封闭函数。这个用例很弱:只需命名封闭函数即可!此外,arguments.callee实质上阻碍了诸如内联函数之类的优化,因为如果访问arguments.callee,必须使其能够提供对未内联函数的引用。严格模式功能的arguments.callee是不可删除的属性,在设置或检索时会抛出该属性。


4
+1尽管在IE8中是一个小错误,但在IE8或更低版本中,myself它实际上在封闭变量环境中可见,并且它引用了实际函数的副本myself。您应该能够将外部引用设置为null
user113716 2011年

感谢你的回答!两者都有帮助,并以两种不同方式解决了问题。最后,我随机决定接受:P
Samthere,2011年

只是让我了解。在每次收益上乘以函数的原因是什么?return n * myself(n-1);
chitzui

为什么函数会这样工作?jsfiddle.net/jvL5euho/18,如果循环了4次。
Prashant Tapase

根据一些参考参数,callee将无法在严格模式下工作。
克鲁纳尔·林巴德

10

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

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

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


6
我认为这已被弃用(并且在严格模式下不允许使用)
Arnaud Le Blanc

@Felix:是的,“严格模式”会给出一个TypeError,但我还没有找到任何正式声明arguments.callee (或任何违反严格模式的)超出“严格模式”的东西。
user113716 2011年

感谢你的回答!两者都有帮助,并以两种不同方式解决了问题。最后,我随机决定接受:P
Samthere,2011年

6

您可以使用Y-combinator:(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);
  }
});

5

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

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

3

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

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值是“倒数” 。这是因为在其中我们会记录这些值的位置,作为函数复发日志记录之前-所以,我们基本上保持筑巢越陷越深到调用堆栈 之前记录发生。

一旦递归满足最终的调用堆栈项,它就会在函数调用“ 蹦蹦跳跳 ”的同时,counter在最后一个嵌套调用内部发生第一个递增。

我知道这不是对Questioner的代码的“修复”,但是考虑到标题,我认为我通常会例举Recursion以更好地完全理解递归。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.