浅谈JavaScript中的闭包
简述闭包
谈及闭包,每个人都有不同的理解,“一千个读者眼中就会有一千个哈姆雷特。”。笔者认为,简述闭包就是,让开发者可以从内部函数访问外部函数的作用域,即使在父函数关闭之后。
实例
1 | var add = (function () { |
看明白了吗?如果没有的话,让我们重头来过。
解释引例1
全局变量
我们知道,在JavaScript中,函数能够访问
内部
定义的所有变量:
1 | function myFunction() { |
也能够访问函数
外部
定义的变量:
1 | var a = 4; |
在最后这个例子中,
a
是全局
变量。在网页中,全局变量属于 window 对象。
全局变量能够被页面中(以及窗口中)的所有脚本使用和修改。
在第一个例子中,
a
是局部
变量。局部变量只能用于其被定义的函数内部。对于其他函数和脚本代码来说它是不可见的。
拥有相同名称的全局变量和局部变量是不同的变量。修改一个,不会改变其他。
不通过
关键词var
创建的变量总是全局的,即使它们在函数中创建(即变量提升
)。
1 | function A(){ |
此时,x相当于全局变量。
变量的生命周期
全局变量活得和您的应用程序(窗口、网页)一样久。
局部变量活得不长。它们在函数调用时创建,在函数完成后被删除。
解释引例2
假设您想使用变量来计数,并且您希望此计数器可用于所有函数。
您可以使用全局变量和函数来递增计数器:
1 | // 初始化计数器 |
但是这个计数器存在一些问题,我们可以通过其他方式改变
counter
的值不一定需要通过add()
方法,又或者说,这个全局变量counter
会受到其他函数或者方法的影响而改变,这时我add()
方法统计出来的counter
的值,可能不是正确的结果,为了解决这个问题,我们把变量定义移到add()
方法内部:
1 | // 递增计数器的函数 |
此时
counter
为方法内的局部变量,不会受到其他函数或者方法的影响,但是,我们发现,实际上,输出的并不是我们想要的结果。因为每一次执行add
方法时,counter
都会被置0。实际上我们想要的结果是:在开始时,
counter
初始化为0,此后不再执行置0操作,但是每次都会执行+1
操作。那怎么办呢?我们可以这样实现:
1 | var add = (function () { |
我们定义了一个
add
变量,让它等于一个自调用函数:
1 | (function(){})() //自调用函数格式 |
那么我们来解释一下,上述代码片段:
第一次调用
add
方法时,会按照从上到下执行,counter
初始化为0
,然后counter
=counter
+ 1,所以第一次结果输出为1第二次调用
add
方法,注意这里因为第一次调用时返回给我们的是return function () {return counter += 1;}
所以此时实际上add
长这样:
1 | add =function () {return counter += 1; } //couter值为1 |
因为我们并没有去执行第一个匿名函数,所以
counter
值并没有置0,仍然为我们之前改变的1此时
counter = counter(1) + 1
为2第三次也可以这么理解。
上述代码片段也可以改写成:
1 | function counter() { |
总结
我们通过计数器的例子,实实在在的访问并改变了外部函数的变量,除了第一次调用过这个函数之外。后续的每一次变量的访问和改变我们都没有访问外部函数。这就是闭包的经典案列。