繁体   English   中英

了解v8中的JavaScript闭包变量捕获

[英]Understanding javascript closure variable capture in v8

我了解闭包持有对变量的引用的语义,这延长了它的生命周期,使原始变量不受调用堆栈的限制,因此,应特别对待闭包捕获的那些变量。

我还了解,可以根据当前的javascript引擎中的闭包是否捕获相同范围内的变量来进行不同的处理。 例如,

function foo(){
    var a=2;
    var b=new Array(a_very_big_number).join('+');
    return function(){
        console.log(a);
    };
}
var b=foo();

由于没有人在foo保留对b的引用,因此无需将b保留在内存中,因此可以在foo返回时立即释放使用的内存(甚至在进一步的优化下也不会创建)。

我的问题是,为什么v8似乎在每个调用上下文中将所有闭包引用的所有变量打包在一起? 例如,

function foo(){
    var a=0,b=1,c=2;
    var zig=function(){
        console.log(a);
    };
    var zag=function(){
        console.log(b);
    };
    return [zig,zag];
}

zigzag似乎都拥有对ab的引用,即使很显然b对于zig都不可用。 b很大并且zig持续很长时间时,这可能会很糟糕。

但是从实现的角度来看,我不明白为什么这是必须的。 根据我的知识,无需调用eval即可在执行前确定作用域链,从而可以确定引用关系。 引擎应注意,当zig不再可用时,请执行a以便引擎将其标记为垃圾。

chrome和Firefox似乎都遵守规则。 标准是否说任何实现都必须这样做? 还是这种实现更实用,更有效? 我很困惑。

主要障碍是可变性。 如果两个闭包共享相同的var则它们必须以一种可见的方式在另一个闭包中进行更改。 因此,不可能像函数语言那样将引用变量的值复制到每个闭包环境中(绑定是不可变的)。 您需要共享一个指向公共可变堆位置的指针。

现在,您可以将每个捕获的变量分配为堆上的一个单独的单元,而不是一个包含所有变量的数组。 但是,这在空间和时间上通常会更昂贵,因为您需要多个分配和两个间接级别(每个闭包都指向其自己的闭包环境,该闭包环境指向每个共享的可变变量单元格)。 在当前的实现中,它仅是每个作用域的一种分配,并且是一种间接访问变量(单个作用域内的所有闭包都指向同一可变变量数组)。 缺点是某些使用寿命比您预期的更长。 这是一个权衡。

其他考虑因素是实现的复杂性和可调试性。 借助eval类的可疑功能以及调试器可以检查作用域链的期望,基于作用域的实现更加易于处理。

该标准没有对垃圾回收进行任何说明,但提供了一些应该发生的线索。 参考: 标准

外部词法环境当然可以具有自己的外部词法环境。 词法环境可以用作多个内部词法环境的外部环境。 例如,如果一个函数声明包含两个嵌套的函数声明,则每个嵌套函数的词法环境将以周围函数当前执行的词法环境作为其外部词法环境。”

Section 13 Function definition
  step 4: "Let closure be the result of creating a new Function object as specified in 13.2"

Section 13.2 "a Lexical Environment specified by Scope" (scope = closure)

Section 10.2 Lexical Environments:
"The outer reference of a (inner) Lexical Environment is a reference to the Lexical Environment that logically surrounds the inner Lexical Environment.

因此,一个函数将有权访问父级的环境。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM