浅谈JavaScript中的闭包

浅谈JavaScript中的闭包

简述闭包

谈及闭包,每个人都有不同的理解,“一千个读者眼中就会有一千个哈姆雷特。”。笔者认为,简述闭包就是,让开发者可以从内部函数访问外部函数的作用域,即使在父函数关闭之后。

实例

1
2
3
4
5
6
7
8
9
10
var add = (function () {
var counter = 0;
return function () {return counter += 1;}
})();

add();
add();
add();

// 计数器目前是 3

看明白了吗?如果没有的话,让我们重头来过。

解释引例1

全局变量

我们知道,在JavaScript中,函数能够访问内部定义的所有变量:

1
2
3
4
function myFunction() {
var a = 4;
return a * a;
}

也能够访问函数外部定义的变量:

1
2
3
4
var a = 4;
function myFunction() {
return a * a;
}

在最后这个例子中,a全局变量。

在网页中,全局变量属于 window 对象。

全局变量能够被页面中(以及窗口中)的所有脚本使用和修改。

在第一个例子中,a局部变量。

局部变量只能用于其被定义的函数内部。对于其他函数和脚本代码来说它是不可见的。

拥有相同名称的全局变量和局部变量是不同的变量。修改一个,不会改变其他。

不通过关键词 var 创建的变量总是全局的,即使它们在函数中创建(即变量提升)。

1
2
3
function A(){
x = 5;
}

此时,x相当于全局变量。


变量的生命周期

全局变量活得和您的应用程序(窗口、网页)一样久。

局部变量活得不长。它们在函数调用时创建,在函数完成后被删除。


解释引例2

假设您想使用变量来计数,并且您希望此计数器可用于所有函数。

您可以使用全局变量和函数来递增计数器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 初始化计数器
var counter = 0;

// 递增计数器的函数
function add() {
counter += 1;
}

// 调用三次 add()
add();
add();
add();

// 此时计数器应该是 3

但是这个计数器存在一些问题,我们可以通过其他方式改变counter的值不一定需要通过add()方法,又或者说,这个全局变量counter会受到其他函数或者方法的影响而改变,这时我add()方法统计出来的counter的值,可能不是正确的结果,为了解决这个问题,我们把变量定义移到add()方法内部:

1
2
3
4
5
6
7
8
9
10
11
12
// 递增计数器的函数
function add() {
var counter = 0;
counter += 1;
}

// 调用三次 add()
add();
add();
add();

//此时计数器应该是 3。但它是 1。

此时counter为方法内的局部变量,不会受到其他函数或者方法的影响,但是,我们发现,实际上,输出的并不是我们想要的结果。因为每一次执行add方法时,counter都会被置0。

实际上我们想要的结果是:在开始时,counter初始化为0,此后不再执行置0操作,但是每次都会执行+1操作。那怎么办呢?我们可以这样实现:

1
2
3
4
5
6
7
8
9
10
var add = (function () {
var counter = 0;
return function () {return counter += 1;}
})();

add();//第一次调用 counter值为1
add();//第二次调用 counter值为2
add();//第三次调用 counter值为3

// 计数器目前是 3

我们定义了一个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
2
3
4
5
6
7
8
9
10
11
12
function counter() {
let counter = 0;
return function incrementCount() {
return counter += 1;
}
}

const add = counter();
add(); // 输出 1
add(); // 输出 2
add(); // 输出 3

总结

我们通过计数器的例子,实实在在的访问并改变了外部函数的变量,除了第一次调用过这个函数之外。后续的每一次变量的访问和改变我们都没有访问外部函数。这就是闭包的经典案列。

欢迎关注我的其它发布渠道