在 JavaScript 中真的需要提升以实现相互递归吗?

Is hoisting really necessary in javascript to enable mutual recursion?

本文关键字:实现 递归 JavaScript 真的      更新时间:2023-09-26

在一门在线课程中,Kyle Simpson说下面的代码展示了在javascript中提升的必要性,因为如果不吊装,"其中一个函数总是被声明得太晚了。

a(1)  // 39
function a(foo){
  if (foo > 20) return foo
    return b(foo+2)
}
function b(foo){
  return c(foo) + 1
}
function c(foo){
  return a(foo*2)
}

但这工作得很好。

var a = function(foo){
  if (foo > 20) return foo
    return b(foo+2)
}
var b = function(foo){
  return c(foo) + 1
}
var c = function(foo){
  return a(foo*2)
}
a(1) // 39

那么故事是什么呢? 撇开调用的便利性和放置不谈,是否有任何情况需要吊装?

我关于非悬挂 JS 无法支持相互递归的说法只是出于说明目的的猜想。它旨在帮助理解语言了解作用域中可用变量的需求。这不是确切语言行为的处方。

像提升这样的语言特性——实际上提升并不存在,它只是在编译期间、执行之前提前在作用域环境中声明变量的隐喻——这是一个如此基本的特征,当它与语言的其他特征分开时,它不容易被推理。

此外,仅用JS就不可能完全测试这一假设。OP 中的代码片段仅处理等式的一部分,即它使用函数表达式而不是函数声明来避免函数提升。

我用来比较的语言是 C,例如,它要求在 .h 头文件中声明函数签名,以便编译器知道函数的外观,即使它还没有"看到"它。没有它,编译器就会窒息。从某种意义上说,这是一种手动吊装。C 语言这样做是为了类型检查,但可以想象这种需求的存在还有其他原因。


另一种思考方式是,JS是否是一种编译语言,在执行之前已经发现了所有内容,或者它是否在一次传递中自上而下地解释。

如果JS是自上而下的解释,并且它涉及到一个a()函数的定义,该函数引用了它尚未看到的内部b(),这可能是一个问题。如果该调用表达式以非延迟方式处理,则引擎此时无法弄清楚b()调用的内容,因为尚未处理b()。有些语言是懒惰的,有些是不懒惰的。

照原样,JS在执行之前首先编译,因此引擎在运行任何函数之前已经发现了所有函数(也称为"提升")。JS也将表达式视为惰性,因此这解释了为什么相互递归工作正常。

但是,如果JS

没有提升和/或不懒惰,可以想象JS引擎将无法处理相互递归,因为a()b()之间的循环引用实际上意味着两者之一总是被声明为"太晚"。

这就是我在书中的全部意思。

撇开调用的便利性和放置不谈,没有任何情况需要吊装。

只需确保在使用它们之前声明所有函数即可。

注意:在某些浏览器中,function a(){}会创建名称为 a 的函数,而var a = function(){}则不会(被视为匿名函数)。调试时使用函数名称。你也可以做var b = function a(){}.

第二个代码块工作正常,因为您在初始化所有函数后调用a(1)。尝试以下块:

var a = function(foo){
  if (foo > 20) return foo
    return b(foo+2)
}
var b = function(foo){
  return c(foo) + 1
}
a(1);
var c = function(foo){
  return a(foo*2)
}

这将Uncaught TypeError: c is not a function给出错误,因为function assigned to c没有被吊起。这就是您需要吊装的原因。

因为如果你在第一个代码块中声明函数,所有函数都将被提升,你可以在代码中的任何位置调用a。在其他情况下并非如此。