Javascript 如何存储具有复杂词法作用域和返回结果等函数的激活记录?
我在一些地方读过,比如“访问链接”和“查找非局部变量的显示方法”。然而,似乎没有一个像JavaScript那样涉及到复杂的情况,在JavaScript中,您可以在函数中包含函数,并从函数中返回这些函数,同时保持这些函数的“内部范围” 这是一个复杂的嵌套函数Javascript 如何存储具有复杂词法作用域和返回结果等函数的激活记录?,javascript,scope,compiler-construction,closures,Javascript,Scope,Compiler Construction,Closures,我在一些地方读过,比如“访问链接”和“查找非局部变量的显示方法”。然而,似乎没有一个像JavaScript那样涉及到复杂的情况,在JavaScript中,您可以在函数中包含函数,并从函数中返回这些函数,同时保持这些函数的“内部范围” 这是一个复杂的嵌套函数 设m=foo() m() m() 函数foo(){ 设x=10 功能条(){ 设y=2**x 函数hello(){ 设z=30 函数另一个(){ //地点2 设q=x+y+z 函数嵌套(){ q+=(17*x)-(3*z) //地点1 控制
设m=foo()
m()
m()
函数foo(){
设x=10
功能条(){
设y=2**x
函数hello(){
设z=30
函数另一个(){
//地点2
设q=x+y+z
函数嵌套(){
q+=(17*x)-(3*z)
//地点1
控制台日志(q)
控制台日志(w)
}
返回嵌套
}
//地点3
设w=另一个()
返回w
}
打招呼
}
//地点4
设a=bar()
let nested=a()//hello返回另一个
嵌套的()
嵌套的()
返回嵌套
}
仅当语言的语义不允许非局部变量超出其原始范围时,才能使用显示,因为显示基本上是在堆栈上查找激活记录的有效方法。如果您想要实现真正的闭包,您需要使用一种不同的机制,该机制涉及封闭变量的堆分配。(除非你能证明封闭变量永远不会逃逸。通常是这样,但很难证明。)
在AUUI中,Javascript保留创建闭合变量的函数调用的整个激活记录。但这取决于语言的语义,并且不必要地在激活记录中保留与其他变量的活动链接,以防止它们被及时地垃圾收集。应该只需要保持与实际包含的变量的链接
请注意,有两种不同的情况(同样,这取决于语言语义)。在简单的情况下,捕获的变量是不可变的(尽管它可能包含可变值)。在这种情况下,您可以通过将值作为数据成员添加到闭包中来捕获该值。(闭包对象本身需要堆分配,但在实际创建闭包之前不需要这样做,这可能永远不会发生。) 在更复杂的情况下,变量本身是可变的,并且可能由内部函数进行变异[注1]。在这种情况下,变量本身必须“装箱”;也就是说,封装在用作变量句柄的容器中。但是变异可能发生在变量的作用域终止之前,在这种情况下,变化需要在该作用域中可见。如果变量已装箱,则外部函数需要注意这一点,因为对变量的访问是间接的。因此,将变量装箱到堆中是一项成本,必须在知道有必要之前支付(取决于转义分析的质量) 这就是Lua实现的有趣之处,至少是这样。在许多情况下,它可以避免堆分配箱子,但这样做会增加成本。对于Lua用例来说,这似乎很好,但我不知道在您的情况下这是否是一个好的解决方案
笔记:
仅当语言的语义不允许非局部变量超出其原始范围时,才能使用显示,因为显示基本上是在堆栈上查找激活记录的有效方法。如果您想要实现真正的闭包,您需要使用一种不同的机制,该机制涉及封闭变量的堆分配。(除非你能证明封闭变量永远不会逃逸。通常是这样,但很难证明。) 在AUUI中,Javascript保留创建闭合变量的函数调用的整个激活记录。但这取决于语言的语义,并且不必要地在激活记录中保留与其他变量的活动链接,以防止它们被及时地垃圾收集。应该只需要保持与实际包含的变量的链接
请注意,有两种不同的情况(同样,这取决于语言语义)。在简单的情况下,捕获的变量是不可变的(尽管它可能包含可变值)。在这种情况下,您可以通过将值作为数据成员添加到闭包中来捕获该值。(闭包对象本身需要堆分配,但在实际创建闭包之前不需要这样做,这可能永远不会发生。) 在更复杂的情况下,变量本身是可变的,并且可能由内部函数进行变异[注1]。在这种情况下,变量本身必须“装箱”;也就是说,封装在用作变量句柄的容器中。但是变异可能发生在变量的作用域终止之前,在这种情况下,变化需要在该作用域中可见。如果变量已装箱,则外部函数需要知道这一点,因为访问