在 javascript 中真的有必要提升以启用相互递归吗?

Is hoisting really necessary in javascript to enable mutual recursion?

在在线课程中,Kyle Simpson 说以下代码演示了 javascript 中提升的必要性,因为没有提升 "one of the functions would always be declared too late."

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

这是怎么回事?撇开调用的便利性和位置不谈,是否有任何情况需要提升?

第二个代码块工作正常,因为您在所有函数初始化后调用 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。在其他情况下情况并非如此。

撇开调用的方便和放置不谈,没有任何情况需要提升。

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

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

我所说的非提升 JS 无法支持相互递归只是出于说明目的的推测。它旨在帮助理解语言了解作用域中可用变量的必要性。这不是针对确切语言行为的处方。

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

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

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


另一种思考方式是 JS 是否是一种编译型语言,在执行之前一切都已被发现,或者它是否在一次传递中自上而下解释。

如果 JS 是自上而下解释的,并且它到达了一个 a() 函数的定义,该函数引用了它尚未在其中看到的 b(),那可能是个问题.如果那个调用表达式是非延迟处理的,那么引擎在那一刻无法弄清楚 b() 调用是关于什么的,因为 b() 还没有被处理。有些语言是惰性的,有些是非惰性的。

照原样,JS 在执行之前首先被编译,所以引擎在 运行 之前发现了所有函数(aka "hoisting")。 JS 也将表达式视为惰性的,所以一起解释了为什么相互递归可以正常工作。

但是如果JS没有提升and/or不是懒惰,可以想象JS引擎无法处理相互递归,因为a()b()之间的循环引用会实际上意味着总是声明两者之一 "too late".

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