闭包

133 阅读4分钟

一、作用域链

闭包这个概念最初接触起来是有一定难度的,它的难点在我看来,是对 JavaScript 中作用域的理解,因此我们首先再来回顾一下作用域的相关知识点:

  1. 在 JavaScript 中,变量的定义并不是以代码块作为作用域的,而是以函数作为作用域的。
  2. 也就是说,如果一个变量是在某个函数中定义的,那么,它在函数以外的地方是不可见的。
  3. 而如果,该变量是定义在 if 或 for 这样的代码块中的,那么,它在代码块以外的地方是可见的。
  4. 所谓 全局变量,是指定义在所有函数之外的变量,定义在某个函数中的变量(显式的)则称之为局部变量(私有)。
  5. 定义在函数之外的变量在函数内是可见的,反之则不然。

我们来看个例子:

var a = 123;
function f(){
    var b = 456;
    return a;
}

//调用一下
f();    //123
b    //ReferenceError: b is not defined

在这里,变量 a 是属于全局域的,而变量 b 的作用域就在函数 f( ) 内了。因此:

  • 在 f( ) 内,a 和 b 都是可见的
  • 在 f( ) 外,a 是可见的,b 则不可见

在下面的例子中,如果我们在函数 outer( ) 中定义了另一个函数 inner( ),那么,在 inner( ) 中可以访问的变量既来自它自身的作用域,也可以来自其“父级”作用域。

var global = 1;
function outer(){
    var outer_local = 2;
    function inner(){
        var inner_local = 3;
        return inner_local + outer_local + global;
    }
    return inner();
};

outer();    //6

像这样,这就形成了一条 作用域链 (scope chain),该链的长度(或深度)则取决于我们的需要。

二、利用闭包突破作用域链 

单纯的去理解闭包的概念是什么,在我看来,没有太大的意义,因此,从它能干什么?它的作用是什么?这个角度,我觉得更能说明它本身。先来看一个例子:

var  a = "global variable";
var F = function(){
    var b = "local variable";
    var N = function(){
        var c = "inner variable";
        return b;
    };
    return N;
};

从上面的例子中,我们可以看出:

  • 变量 b 在函数 F( ) 以外是不可见的:

b    //ReferenceError: b is not defined

  • 函数 F( ) 和函数 F( ) 的 私有函数 N( ) ,都是一个返回函数的函数:函数 F( ) 的返回值是它的私有函数 N 的引用,而私有函数 N( )的返回值,是与其”同处一室“的变量 b ;
  • 在作用域链的影响下私有变量 b 在私有函数 N( ) 中是可见的;  

那么,接下来就是有趣的地方了,即当我们把这个 返回函数的函数 F( ) 赋值给另一个全局变量的时候,就会生成一个可以访问该函数 F( ) 私有空间的新全局函数:

var newGlobal = F();

newGlobal();    //"local variable"

像这样,我们通过 作用域链+私有函数+返回函数的函数 的形式,就打破了作用域链原本固有的规则(函数内定义的变量在该函数外不可见),使之可见了。这就是闭包。


下面的例子的最终结果与之前相同,但在实现方法上存在一些细微的不同。在这里 F( ) 不再返回函数了,而是直接在函数体内创建一个新的全局函数 newGlobal( ):

var newGlobal;
var F = function(){
    var b = "local variable";
    var N = function(){
        return b;
    };
    newGlobal = N;
};

F();
newGlobal();    //"local varoable"

如上,我们需要声明一个全局函数的占位符。尽管这种占位符不是必须的(见下文),但最好还是声明一下,这样,我们我们仍然通过 作用域链+私有函数+变量(提升)的形式,创建了一个闭包。

另外,我们在《变量的作用域》一文中提到过:在函数中,如果我们声明一个变量时没有使用 var 语句,那么,该变量就会被默认提升为全局变量,因此,上面的例子,还可以写成这样:

var F = function(){
    var b = "local variable";
    var N = function(){
        return b;
    };
    inner = N;
};

f();
inner();    //"local variable"

综上所述,我们究竟是如何利用闭包来突破作用域链束缚的呢?我们只需要将他们升级为全局变量(不使用 var 语句)或通过 F 传递(或返回)给全局空间即可。

本文摘自《JavaScript面向对象编程指南》,分享的目的仅供个人学习和理解,如需转载请备注本文出处,谢谢!