递归调用javascript函数

IT技术 javascript function recursion function-expression
2021-01-26 05:15:24

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

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

5个回答

使用命名函数表达式:

您可以为函数表达式指定一个名称,该名称实际上是私有的,并且仅在 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 中的标识符可以从 FunctionExpression 的 FunctionBody 内部引用,以允许函数递归调用自身。但是,与 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 版禁止在严格模式下使用 arguments.callee() ,但是:

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

谢谢你的回答!两者都很有帮助,并以两种不同的方式解决了问题。最后我随机决定接受哪个:P
2021-03-16 05:15:24
为什么函数是这样工作的?jsfiddle.net/jvL5euho/18 if 循环 4 次后。
2021-03-26 05:15:24
+1 虽然它在 IE8 和更低版本中存在一些问题,但myself实际上在封闭变量环境中是可见的,并且它引用了实际函数副本myself您应该能够设置外部引用null
2021-03-27 05:15:24
根据一些参考 arguments.callee 不会在严格模式下工作。
2021-04-02 05:15:24
只是为了让我明白。在每次返回时乘以函数的原因是什么?return n * myself(n-1);?
2021-04-11 05:15:24

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

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

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

@Felix:是的,“严格模式”会给出TypeError,但我没有发现任何正式声明arguments.callee (或任何违反严格模式)在“严格模式”之外已被弃用的内容。
2021-03-19 05:15:24
谢谢你的回答!两者都很有帮助,并以两种不同的方式解决了问题。最后我随机决定接受哪个:P
2021-03-25 05:15:24
我相信这已被弃用(并且在严格模式下不允许)
2021-04-13 05:15:24

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

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

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

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

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内的最后一个嵌套调用的发生。

我知道这不是对发问者代码的“修复”,但鉴于标题,我想我会一般地举例说明递归,以便更好地理解递归,彻底的。