一、作用域

要理解闭包,首先必须理解Javascript特殊的变量作用域,变量的作用域有两种:全局变量和局部变量。

Javascript语言的特殊之处,就在于函数内部可以直接读取全局变量。

1
2
3
4
5
6
7
  var a=111;

  function a1(){
    alert(a);
  }

a1(); // 111

另一方面,在函数外部无法读取函数内的局部变量。

1
2
3
4
5
function a1(){
    var a=999;
  }

  alert(a); // a is not defined

这里有一个地方需要注意,函数内部声明变量的时候,一定要使用let命令。如果不用的话,实际上声明了一个全局变量!

1
2
3
4
5
6
7
function a1(){
    a=111;
  }

  a1();

  alert(a); // 111

二、从外部读取局部变量

出于某原因,我们在特定的开发环境下需要得到函数内的局部变量。但是,我们知道,正常情况下,这是办不到的,只有通过写另外的代码来达到目的。

可以在函数的内部,再定义一个函数。

1
2
3
4
5
6
7
8
9
function a1(){

    var a=111;

    function a2(){
      alert(a); // 111
    }

  }

函数a2在函数a1的作用域内,所以现在a1内部的局部变量,对a2都是可见的。但f2作用域内的局部变量,对f1就是不可见的。这就是Javascript语言特有的”链式作用域”结构,作用域内的子对象可以看见当前作用域内和当前作用域所有父级内部的局部变量,但是父级对子对象的局部变量是不可见的。

如下a2可以访问a1中的局部变量,那么只要把f2作为暴露出来,我们就可以在f1外部访问它的内部变量了。

1
2
3
4
5
6
7
8
9
10
11
12
13
function a1(){

    var a=111;

    function a2(){
      alert(a);
    }
    return a2;
  }

  var test=a1();

test(); // 111

三、什么是闭包

上一节代码中的a2函数,就是闭包。各种专业文献上的”闭包”定义非常抽象,很难看懂。我的理解是:变量私有化,并且在需要时也能够通过变通方法读取。

由于在Javascript语言中,作用域中的变量 只能在作用域内才能访问,可以把闭包简单理解成实现私有成员;保护命名空间;避免污染全局变量。可以使变量长期驻留在内存中。

四、闭包的应用场景

闭包可以用在许多地方。它的最大用处有两个,一是可以在外部访问作用域内的变量,另一个就是让这些变量的值始终保持在内存中。

怎么来理解这句话呢?请看下面的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

function a1(){

    var a=111;

    aAdd=function(){a+=1}

    function a2(){
      alert(a);
    }

    return a2;

  }

  var test=a1();

test(); // 111

  aAdd();

test(); // 112

test 实际上 的等于a2函数,a2函数是a1对外暴露a变量的接口。test执行了两次,第一次的值是111,第二次的值是112。这证明了,函数a1中的局部变量a一直保存在内存中,并没有在a1调用后被自动清除。

导致这样的根本原因就在于a1是a2的父函数,而a2被赋给了一个全局变量,这导致a2始终在内存中,而a2的存在依赖于a1,因此a1也始终在内存中,不会在调用结束后,被垃圾回收机制回收。

这段代码中aAdd可以直接在函数外部执行 是由于他没有使用var或let关键字,因此他是全局变量。