JavaScript和尾调用

时间:2012-02-25 17:45:44

标签: javascript algorithm v8

根据v8票,它说

尾部调用消除与JavaScript不兼容,因为它在真实中使用     世界。请考虑以下事项:

function foo(x) {
  return bar(x + 1);
}

function bar(x) {
  return foo.arguments[0];
}

foo(1)

返回1.

它没有明确解释如果JavaScript支持尾调用,foo(1)的值是什么以及为什么?

任何人都可以解释?

3 个答案:

答案 0 :(得分:5)

值得一点解释。对话就像这样开始(代码重新格式化):

  

如果我在Chrome中打开JavaScript控制台并写下:

function fac(n, a) { 
    if (n == 0) { 
        return a; 
    } 
    else { 
        return fac(n - 1, a * n);
    } 
}
fac(100000, 1);
     

我明白了:RangeError:超出了最大调用堆栈大小

     

我认为V8可能是其他编程的一个很好的目标虚拟机   语言,如果它支持尾调用。这是我能看到的唯一重大障碍   对于具有功能特征的语言。

如果您测试代码,您会注意到fac适用于较低的值,返回Infinity以获取较高的值并导致浏览器抛出RangeError(最大调用堆栈大小)超过)以获得更高的价值。

原因是从另一个函数中调用的每个函数都被添加到“调用堆栈”中,这涉及一些内存开销。有了足够的递归,环境就会耗尽内存。


这可以通过“尾部调用消除”在其他语言中工作,或者不需要将调用添加到调用堆栈。例如,可以存在与正常函数不同的函数式事物,因为它们返回时会导致调用函数返回。这可以消除添加到调用堆栈的需要,这意味着递归本质上是无限的。有关深入解释,请参阅维基百科上的tail call文章。

对上述消息的响应只是提供了一个原因,即尾部调用消除(从调用堆栈中删除函数)与其他功能(Function#arguments)不兼容,能够使用非标准功能。

答案 1 :(得分:2)

  

它没有清楚地解释如果JavaScript支持尾调用什么   将是foo(1)的价值和为什么?

执行1时,该值为foo(1)因为foo函数返回bar函数的结果而bar函数除了读取第一个函数之外什么都不做带有foo的{​​{1}}函数的参数(foo.arguments[0]是每个函数可用的隐含对象,用于读取传递给函数的参数)并返回它。当你这样做时,arguments的第一个参数恰好是foo

1

以下是细分:

foo(1);

function foo(x) { return bar(x + 1); // foo calls bar function which returns 1 } function bar(x) { return foo.arguments[0]; // bar reads first argument passed to foo which is 1 } foo(1); // 1 is x in foo function 函数只读取bar的第一个参数(通过foo)并返回它,因为没有添加。

答案 2 :(得分:1)

为了清楚起见,尾调用消除是一种节省堆栈空间的优化技术,特别适用于递归。当函数以对另一个函数的调用结束时,尾调用消除可以避免分配另一个堆栈帧来调用被调用函数。相反,它将重用(破坏和重新利用)调用函数的堆栈帧,因为它(可能)不再需要它。但是,该示例显示JavaScript代码可能仍需要调用者的堆栈帧。

  

它没有清楚地解释如果JavaScript支持尾调用什么   将是foo(1)的价值和为什么?

如果支持尾部调用消除,则在对foo进行尾调用时将销毁有关bar调用的信息,因此foo.arguments[0]将是错误。如果给定代码起作用,则无法进行尾调用消除。