Blog A Paranoid Guy

关于闭包的理解


说起 闭包 这个词,其实没有一个非常明确的概念。

官方的解释:

一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。闭包的特点:

  1. 作为一个函数变量的一个引用,当函数返回时,其处于激活状态
  1. 一个闭包就是当一个函数返回时,一个没有释放资源的栈区

看起来有些抽象,说白了可以这样理解:闭包 就是有权访问另一个函数作用域中变量的函数

即使这么说,还是难以理解,这方面必须先从 JavaScript 的作用域说起:


作用域链

先来看一个例子:

    var foo = "Hello";

    function f() {

        console.log(foo); 

        var foo = "I am li";

        console.log(foo); 
    }
    
    f();
    console.log(foo); 
    console.log(foo1);

看完之后,我说下答案。从上往下依次为:”undefined” , “I am li”, “Hello” , “JS报错”

后三个输出的值都可以理解,但是第一个呢?不是声明了一个全局变量 foo 吗?这就是标识符在作用域链中的寻找,是这样的:

JS是没有块级作用域的,但有执行环境。当创建第一个 foo 变量时,首先在全局的执行环境创建一个变量对象,并把 foo 变量放入这个对象中。然后创建函数 f1 ,此时创建函数自己的执行环境,并把函数中的变量(包括 arguments 对象和内部对象)放入一个活动对象中。其实函数自己的执行环境和全局执行环境是有联系的,当函数被创建时, 会拷贝全局执行环境的变量对象。当函数内部变量被使用时,现在函数的活动对象中需找有没有这个变量的定义。如果没有,再向上一级需找,依次向上,直到全局环境中的对象。这就是所谓的 作用域链 。上面例子的图解如下:

这就好解释为什么第一个输出为 undefined 原因了:首先在函数的执行环境中寻找,找到了 foo 的标识符,然后就不继找下去了,foo 当然是 undefined

下面看闭包:


闭包

还是先来看一个例子:

    function f1() {

        var i = 0;

        return function () {
            i++;
            console.log(i);
        }
    }

    var f3 = f1();

    f3();
    f3();

这应该是最简单的一个闭包了:一个函数内部定义了另一个函数然后返回。按照我们习惯的理解,两个 f3 被调用的结果都应该是 1 吧。事与愿违,第一个输出是 1 ,第二个输出是 2 ,如果继续调用下去, i 的值会依次加下去。

上面已经说过作用域链了,函数的执行环境被创建时,[[scope]] 是一个函数的内部属性,实际上拷贝了外部作用域的环境对象,然后函数内部的活动对象被创建。这时候,函数的作用域链是 活动对象 + [[scope]]保存的对象

看一个例子:

    var x = 10;

    function foo() {

        var y = 20;

        function bar() {

            var z = 30;

            console.log(x + y + z);
        }

        return bar();
    };

    foo();

从全局说起, 全局变量 x 创建后,被加入作用域链 [scope chain] 此时的全局变量对象(称之为 VO )是 :

    VO = {
        x: 10,
        foo: <function>
    }

作用域链 [[scope chain]] 为:

    [scope chain] = {
        VO = {...}
    }

函数 foo 被创建时,[[scope]] 自动创建, foo[[scope]] 为:

    [[scope]] = {
        VO = {...}
    }

看出来了吧,[[scope]] 拷贝的是外部作用域的 `变量对象/活动对象

当函数被激活时,即foo 被调用时,foo 的活动对象(称之为 AO1 )为:

    AO1 = {
        y: 20,
        bar: <function>
    }

此时的作用域链应为:

    [scope chain] = {
        VO = {...},
        AO1 = {...}
    }

同理,函数函数 bar 被创建时,bar[[scope]] 为:

    [[scope]] = {
        VO: {...},
        AO1: {...}
    }

当函数 bar 被调用时,bar 的活动对象(称之为 AO2 )为:

    AO2 = {
        z: 30
    }

此时的作用域链为:

    [scope chain] = {
        VO: {...},
        AO1: {...},
        AO2: {...}
    }

这就是上面例子执行的全过程!

图解如下:

可以看到,函数的 [[scope]] 引用着外层函数的 [[scope]],外层函数的 [[scope]] 引用着全局的 VO 变量对象。于是,当内层函数不被销毁时,始终引用着外层作用域的 变量对象/[[scope]] ,这就是闭包的原理。

好了,大概我的理解就是这些。额,这么晚了,该洗洗睡了<( ̄ˇ ̄)/


Similar Posts

上一篇 常用正则

下一篇 常用meta整理

Comments