理解javascript闭包

1.闭包是什么

官方解释:闭包是一个拥有很多变量和绑定了这些变量的环境的表达式(其实就是函数),因而这些变量也是该表达式的一部分。这个定义虽然太学术,但是告诉我们两个信息:

1)闭包是一个函数

2)函数中有很多变量

上面两个是构成闭包的两个主要条件。

下面我们用通俗的话来解释一下:js中的所有函数都是闭包(因为函数中的局部变量只能函数内部访问),但是嵌套函数产生的闭包更加强大,也是我们现在所探讨的闭包。

如果上面的解释还不够通俗,下面的终极解释我想你一定能够看懂:

有一个函数a,函数a中嵌套了一个函数b,如果函数b被函数a外部的一个变量引用,就创建了一个闭包。

下面我们来看看具体如何通过代码来创建闭包,以加深上面概念的理解。

2.创建闭包

在创建闭包之前,首先要明白两个概念,一个是变量的作用域,一个事js中的作用域链,第一点我们简单说一下,第二点自己去查资料。

在Js中变量根据作用域的不同可以分为全局变量和局部变量(事实上很多语言都是这样),在js中,如果一个变量没有定义在任何函数中,则为全局变量;相对应的,定义在函数中的变量就是局部变量,但是如果函数中变量在声明时没有使用var关键字,则其仍然会称为全局变量。我们来看例子。

function f() {
            a = 1;//没有使用var,所以在函数外部也可以访问
        }
        f();
        alert(a);

  

下面我们看看如何创建闭包,看下面的函数

     function f1() {
            var a = 10;
            a++;
            alert(a);
        }
        var func1 = f1();
        func1;
        func1;

希望你能猜对上面代码的运行结果,只输出一个11。在函数f1的外部创建了变量func1,然后指向由函数f1的返回值。当执行完代码func1之后,这个对象就没有引用了,所以会被垃圾回收,对象中的变量a同样也会被回收;所以当再次执行func1时就不会有输出了。

从上面的代码,希望你能明白这样一个道理:js中是有垃圾回收机制的。当一个对象没有变量引用的时候,这个对象就会被回收。

再来看下面的代码:

function f1() {
            var a = 10;
            function f2() {
                a++;
                alert(a);
            }
            return f2;
        }
        var func1 = f1();
        func1();
        func1();

  

上面代码的输出结果为11,12。

执行完第一句func1()之后,对象应该被回收,第二句func1();应该没有输出猜对呢,这是为什么呢?

我们来分析一下。

首先看var func1=f1();这行代码执行之后,func1是什么。在函数f1中返回的是函数f2,所以func1的值其实是函数f2。按理说当执行完这行代码之后,函数f1的使命已经完成,应该被垃圾回收才是,你们变量a也会被清除,但是执行代码func1()之后的结果居然为11,这说明函数f1中的变量a没有被清除,那么肯定函数f1也没有被垃圾回收。这是为什么?

我们前面说过,一个对象如果被垃圾回收的条件是什么,那就是没有变量引用这个对象。我们来看看上面的代码。函数f2中对函数f1中的变量a进行++操作,也就是说在函数f2中引用了函数f1中的变量,也就是函数f2引用了函数f1。而代码var func1=f1();其实是将函数f2返回给变量func1,也就是说变量func1引用了函数f2,而函数f2由引用了函数f1,这种间接引用的结果就是函数f1一直被变量引用着,所以一直无法被垃圾回收。

上面的情况就是闭包,我们再回顾一下闭包的定义:如果函数a中的嵌套函数b被函数a外部的变量引用,就创建了闭包。

综合上面的讨论,我们可以看出闭包的作用是什么

3.闭包的作用

1)变量的安全性:我们无法在函数f1的外部直接访问其局部变量a,只能通过函数f2来访问,而在函数f2中我们可以写代码进行安全性的控制,这是不是和c#中类的属性很像。所以我们可以将函数f2看成是函数f1的一个属性,这个属性只有setter方法,而将局部变量a看成是函数f1的私有字段,只能通过公共属性f2才能访问f1中的私有字段a。

2)让变量的值始终保存在内存中。这个已经非常清晰了,通过闭包,函数f1中的变量a没有被回收,而是一直保存在内存中。