93

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

/* 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

4

5 回答 5

149

使用命名函数表达式:

您可以为函数表达式指定一个实际上是私有的名称,并且仅在 ifself 函数内部可见:

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 中的标识符不能被引用,也不影响包含 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 版禁止在严格模式下使用 arguments.callee() ,但是:

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

于 2011-08-15T12:56:58.533 回答
10

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

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

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

于 2011-08-15T12:58:02.913 回答
6

您可以使用 Y 组合器:(维基百科

// 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);
  }
});
于 2015-09-29T18:24:11.473 回答
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);
于 2015-05-29T23:36:43.280 回答
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发生在最后一个嵌套调用内部。

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

于 2014-10-18T00:07:22.467 回答