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 控制

我在一些地方读过,比如“访问链接”和“查找非局部变量的显示方法”。然而,似乎没有一个像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
控制台日志(q)
控制台日志(w)
}
返回嵌套
}
//地点3
设w=另一个()
返回w
}
打招呼
}
//地点4
设a=bar()
let nested=a()//hello返回另一个
嵌套的()
嵌套的()
返回嵌套

}
仅当语言的语义不允许非局部变量超出其原始范围时,才能使用显示,因为显示基本上是在堆栈上查找激活记录的有效方法。如果您想要实现真正的闭包,您需要使用一种不同的机制,该机制涉及封闭变量的堆分配。(除非你能证明封闭变量永远不会逃逸。通常是这样,但很难证明。)

在AUUI中,Javascript保留创建闭合变量的函数调用的整个激活记录。但这取决于语言的语义,并且不必要地在激活记录中保留与其他变量的活动链接,以防止它们被及时地垃圾收集。应该只需要保持与实际包含的变量的链接


请注意,有两种不同的情况(同样,这取决于语言语义)。在简单的情况下,捕获的变量是不可变的(尽管它可能包含可变值)。在这种情况下,您可以通过将值作为数据成员添加到闭包中来捕获该值。(闭包对象本身需要堆分配,但在实际创建闭包之前不需要这样做,这可能永远不会发生。)

在更复杂的情况下,变量本身是可变的,并且可能由内部函数进行变异[注1]。在这种情况下,变量本身必须“装箱”;也就是说,封装在用作变量句柄的容器中。但是变异可能发生在变量的作用域终止之前,在这种情况下,变化需要在该作用域中可见。如果变量已装箱,则外部函数需要注意这一点,因为对变量的访问是间接的。因此,将变量装箱到堆中是一项成本,必须在知道有必要之前支付(取决于转义分析的质量)

这就是Lua实现的有趣之处,至少是这样。在许多情况下,它可以避免堆分配箱子,但这样做会增加成本。对于Lua用例来说,这似乎很好,但我不知道在您的情况下这是否是一个好的解决方案


笔记:
  • 有一种非常常见的情况,变量是可变的,因为在单个初始化表达式中无法方便地计算其值,但所有突变都发生在捕获变量之前。这样的变量可以被视为是不可变的(或者被计算出来,然后用相同的名称生成另一个变量的值)。这种情况很容易发现。(还有一些变量的最后一次变异发生在捕获之后,但在第一次调用捕获函数之前。这些变量通常也可以被检测到。)这些变量可以被捕获,就像它们是常量一样,只需将值放入闭包中

    非正式调查表明,绝大多数捕获的变量要么是常量,要么是捕获后的常量,这可以大大减少对堆分配框的需求


  • 仅当语言的语义不允许非局部变量超出其原始范围时,才能使用显示,因为显示基本上是在堆栈上查找激活记录的有效方法。如果您想要实现真正的闭包,您需要使用一种不同的机制,该机制涉及封闭变量的堆分配。(除非你能证明封闭变量永远不会逃逸。通常是这样,但很难证明。)

    在AUUI中,Javascript保留创建闭合变量的函数调用的整个激活记录。但这取决于语言的语义,并且不必要地在激活记录中保留与其他变量的活动链接,以防止它们被及时地垃圾收集。应该只需要保持与实际包含的变量的链接


    请注意,有两种不同的情况(同样,这取决于语言语义)。在简单的情况下,捕获的变量是不可变的(尽管它可能包含可变值)。在这种情况下,您可以通过将值作为数据成员添加到闭包中来捕获该值。(闭包对象本身需要堆分配,但在实际创建闭包之前不需要这样做,这可能永远不会发生。)

    在更复杂的情况下,变量本身是可变的,并且可能由内部函数进行变异[注1]。在这种情况下,变量本身必须“装箱”;也就是说,封装在用作变量句柄的容器中。但是变异可能发生在变量的作用域终止之前,在这种情况下,变化需要在该作用域中可见。如果变量已装箱,则外部函数需要知道这一点,因为访问