【问题标题】:Is this a tail call? (Javascript)这是尾声吗? (Javascript)
【发布时间】:2015-06-09 21:16:30
【问题描述】:

假设你有一个递归函数,比如:

Blah.prototype.add = function(n) {
    this.total += n;
    this.children.forEach(function(child) {
        child.add(n);
    });
};

child.add() 是尾号吗?如果不是,可以这样写吗?

【问题讨论】:

  • 我会这么说。即使它在循环中,它仍然是执行的最后一个操作。
  • 我不确定...我在考虑传统的for(i=0;i<children.length;i++) 循环,并认为n < children.length 比较将是最后一个操作。不确定 forEach,如果是这种情况,为什么 forEach 比 for 更受欢迎。
  • 大概 JS 中的 tail call 会使用 return someFn() 还允许对启动器函数进行垃圾收集。
  • forEach() 在迭代时不会看到推入数组的项目...
  • 这是回调函数中的尾调用,但forEach 仍需要保持其循环状态。因此,此循环与 for 循环之间的堆栈使用实际上几乎没有区别。

标签: javascript tail-call-optimization


【解决方案1】:

是的,这是一个尾声:

function(child) {
    child.add(n);
// ^ tail
}

这里没有什么是尾递归的,因为它不是直接递归调用。

另外,this.children.forEach(…)add 方法中的尾调用。

但是,在本机 forEach 方法中调用回调可能没有优化尾调用(除了最后一个之外的所有方法都不能优化)。你可以通过重写你的函数来强制它

Blah.prototype.add = function(n) {
    "use strict";
    this.total += n;
    let l = this.children.length;
    if (!l--)
        return;
    for (let i=0; i<l; i++)
        this.children[i].add(n);
    this.children[i].add(n); // tail-recursion
};

请注意,如果您不同时return 他们的结果,这些尾调用都不会被优化。

【讨论】:

  • 你确定add 是尾递归的吗?即使使用了简单的 for 循环构造,执行的最新操作也将是循环条件检查,因此不会是尾递归的,不是吗?我认为递归调用必须是尾递归算法的最新执行表达式。
  • @plalx:我没有说add 是尾递归的。如果它与forEach 及其回调完全相互递归 - 它不会调用自身。我只是说add 包含一个尾调用。
  • 谢谢,是的,我应该清楚我问的是 child.add 是否是 add 函数的尾调用,而不是 forEach 回调。
  • @pixelmike:调用只能相对于当前执行的函数位于尾部位置。调用堆栈或词法范围内其他地方的函数无关紧要。
  • Rauschmayer disagrees(您必须稍微向上滚动,顶部的横幅会遮挡目标)。你能指出它在规范中定义child.add(n)forEach 回调的尾部位置吗?
【解决方案2】:

【讨论】:

  • 是的,我认为优化还没有到位,但我正在努力为它做好准备。感谢您的链接。
  • 你链接的那个问题已经过时了。 ES6 进行了适当的尾调用优化。
  • 假设它是正确的 kangax.github.io/compat-table/es6 从今天起没有使用它
  • @exussum:是的,草案还没有被接受,但是所有的功能已经在引擎中逐步实现。如您所见,一些转译器已经进行了优化。
  • 虽然我同意该链接仍然有效。早期采用可能会改变,但没有任何尾部优化。可能会有语法更改以允许尾部调用优化。它是未知的,除非您遵循草案,否则无法实施
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-08-04
  • 2017-03-17
  • 1970-01-01
相关资源
最近更新 更多