在一门在线课程中,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
。在其他情况下并非如此。