2019-04-08

2019-04-09  本文已影响0人  蓝子_23d0

作用域链与闭包

作用域

这里的标识符,指的是变量名或者函数名

JavaScript代码的整个执行过程,分为两个阶段,代码编译阶段与代码执行阶段。编译阶段由编译器完成,将代码翻译成可执行代码,这个阶段作用域规则会确定。执行阶段由引擎完成,主要任务是执行可执行代码,执行上下文在这个阶段创建。


image.png

作用域链

image.png
我们知道函数在调用激活时,会开始创建对应的执行上下文,在执行上下文生成的过程中,变量对象,作用域链,以及this的值会分别被确定。
作用域链,是当前环境与上层环境的一系列变量对象组成,它保证了当前环境对符合访问权限的变量和函数的有序访问。
举个栗子:)
var a = 20;
function test() {
   var b = a + 10;

    function innerTest(){
            var c =10;
             return b+c;
     }
return innerTest();
}
test();

在上面的栗子:)中,全局,函数test,函数的innerTest的执行上下文先后创建。我们设定他们的变量对象分别为VO(global),VO(test),VO(innerTest)。而innerTest的作用域链,则同时包含了这三个变量函数,所以innerTest的执行上下文可如下表示。

innerTestEC = {
    VO: {. . .}.     //变量对象
    scopeChain: [VO(innerTest), VO(test),VO(global)],   //作用域链
     }

我们可以直接用一个数组来表示作用域链,数组的第一项scopeChain[0]为作用域链的最前端,而数组的最后一项,为作用域的最末端,所有的最末端都为全局变量对象。

很多人会误解为当前作用域与上层作用域为包含关系,但其实不是。以最前端为起点,最末端为终点的单方向通道我认为是更加贴切的形容。如图:


image.png

注意,因为变量对象在执行上下文进入执行阶段时,就变成了活动对象,这一点在上一篇文章中已经讲过,因此图中使用了AO来表示。Active Object

作用域链是由一系列变量对象组成,我们可以在这个单向通道中,查询变量对象中的标识符,这样就可以访问到上一层作用域中的变量了。

闭包

闭包是一种特殊的对象。

它由两部分组成。执行上下文(代号A),以及在该执行上下文中创建的函数(代号B)。

当B执行时,如果访问了A中变量对象中的值,那么闭包就会产生。

在大多数理解中,包括许多著名的书籍,文章里都以函数B的名字代指这里生成的闭包。而在chrome中,则以执行上下文A的函数名代指闭包。
举一个简单的栗子:)

var fn = null;
function foo() {
    var a = 2;
    function innnerFoo() {
        console.log(a);
    }
    fn = innnerFoo; // 将 innnerFoo的引用,赋值给全局变量中的fn
}

function bar() {
    fn(); // 此处的保留的innerFoo的引用
}

foo();
bar(); // 2

在上面的例子中,foo()执行完毕之后,按照常理,其执行环境生命周期会结束,所占内存被垃圾收集器释放。但是通过fn = innerFoo,函数innerFoo的引用被保留了下来,复制给了全局变量fn。这个行为,导致了foo的变量对象,也被保留了下来。于是,函数fn在函数bar内部执行时,依然可以访问这个被保留下来的变量对象。所以此刻仍然能够访问到变量a的值。

这样,我们就可以称foo为闭包。


参考链接

上一篇下一篇

猜你喜欢

热点阅读