JavaScript和尾部调用

JavaScript and Tail Call

本文关键字:调用 尾部 JavaScript      更新时间:2023-09-26

根据v8票证,它说

尾部调用消除与JavaScript不兼容,因为它在实际中使用世界考虑以下内容:

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

返回1。

它没有清楚地解释如果JavaScript支持tail调用,那么foo(1)的值是什么,为什么?

有人能解释吗?

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

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

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

我得到这个:范围错误:最大调用堆栈大小超过

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

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

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


这在其他语言中是通过"尾部调用消除"或消除将调用添加到调用堆栈的需要来实现的。例如,类似函数的东西可能存在,与正常函数不同的是,当它们返回时,会导致调用函数返回。这可以消除添加到调用堆栈的需要,这意味着递归本质上可以是无限的。有关详细解释,请参阅维基百科上的尾注文章。

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

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

执行foo(1)时,该值将为1,因为foo函数返回bar函数的结果,而bar函数除了用foo.arguments[0]读取foo函数的第一个参数外什么也不做(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的第一个参数(通过foo.arguments[0])并返回它,因此不进行加法运算。

需要明确的是,尾部调用消除是一种节省堆栈空间的优化技术,对递归特别有用。当一个函数以对另一个函数的调用结束时,尾部调用消除可以避免分配另一个堆栈帧来调用被调用的函数。相反,它将重用(销毁并重新调整用途)调用函数的堆栈帧,因为(可能)不再需要它。然而,该示例显示JavaScript代码可能仍然需要调用方的堆栈框架。

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

如果支持尾部调用消除,则在对bar进行尾部调用时,有关foo调用的信息将被销毁,因此foo.arguments[0]将是一个错误。如果给定的代码要工作,那么尾调用消除是不可能的。