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

时间:2018-12-31 08:51:32

标签: javascript closures v8

我理解语义,即闭包对变量的引用延长了它的生命周期,使原始变量不受调用堆栈的限制,因此对闭包捕获的那些变量应进行特殊处理。

我还理解,同一范围内的变量是否可以不同对待取决于它是否被当今的javascript引擎中的闭包捕获。例如,

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

由于没有人在b中保留对foo的引用,因此无需将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对{{1}也不可用}。当zig很大且b持续很长时间时,这可能会很糟糕。

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

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

2 个答案:

答案 0 :(得分:1)

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

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

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

答案 1 :(得分:0)

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

  

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

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.

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