在 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".
这就是我在书中的全部意思。
在在线课程中,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".
这就是我在书中的全部意思。