递归调用javascript函数

时间:2011-08-15 12:51:31

标签: javascript function recursion function-expression

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

/* 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我们致电0copyFunction(3);?我尝试了this(counter-1);,但这给了我错误this is not a function

5 个答案:

答案 0 :(得分:139)

使用命名函数表达式:

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

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中的标识符不能被引用,也不会影响包含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);
}

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

  

(From MDN):在普通代码中,arguments.callee引用封闭函数。这个用例很弱:只需命名封闭函数!而且,arguments.callee实际上阻碍了内联函数之类的优化,因为如果访问arguments.callee,必须能够提供对非内联函数的引用。用于严格模式函数的arguments.callee是一个不可删除的属性,在设置或检索时抛出。

答案 1 :(得分:8)

您可以使用arguments.callee [MDN]

访问该功能
if (counter>0) {
    arguments.callee(counter-1);
}

然而,这将在严格模式下中断。

答案 2 :(得分:5)

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

<?php

/**
 * The entity class.
 */
class Test
{
    public $id;
    public $info;
}

class Test_model extends Base_repository
{
    /**
     * Tell what table we are using.
     */
    public function __construct()
    {
        parent::__construct();

        $this->table = 'test';
    }

    /**
     * "Overload" save method and call it from the parent.
     * 
     * @param  test $item Make use of the Dependency Injection.
     * @return void
     */
    public function save(Test $item)
    {
        parent::save($item);
    }
}

答案 3 :(得分:4)

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

答案 4 :(得分: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的值是“向后”。这是因为我们记录这些值的位置,因为函数在记录之前重复 - 所以,我们基本上更深入地嵌套到调用堆栈 日志记录发生之前。

一旦递归符合最终的调用堆栈项,它就会 trampolines “out”函数调用,而counter的第一个增量发生在最后一个嵌套调用中。

我知道这不是查询者代码的“修复”,但鉴于标题,我认为我通常会举例说明递归,以便更好地理解递归,直接。