# 闭包
# 问题产生
你现在想实现一个计数器!于是开始开始捣鼓代码。
var times = 0;
function inc () {
times ++;
return times;
}
2
3
4
5
接下来如果调用inc(),一个简易的计数器就做好了!此时你的小伙伴来找你合并代码,她的代码中也有一个times,另一个问题因此而产生。这个times变量在全局变量中,不同逻辑代码因此共用了同一个变量。为了正常运行代码,你俩其中一个必须把变量给改名了......
改来改去是个人都会烦。闭包可以解决这个问题,所以为了与她解决冲突,你一定要把它学会!
# 闭包思想
来看一些代码吧
var outer = 1;
function that () {
var inner = 0;
}
2
3
4
可以自己试试看,在that函数外部,无法调用inner变量,但是在that函数内部,调用外部的outer变量。而且函数that()运行结束后,其内部的变量inner会被释放。
那有没有一种方法,在调用函数后,内部的变量还能保存在内存中且能被外部的程序语句调用呢?
这稍微有点像是面向对象中类的实例所拥有的私有变量了,可以自己感受一下。
class Person {
private int age = 18;
public int getAge () {
return this.age;
}
}
2
3
4
5
6
现在开始感受闭包的魅力吧!
这是是一个用闭包方式写的一个定时器,它会返回一个它的子函数,子函数中返回的是createCounter()中的变量times,而times并不是全局变量,createCounter()函数外部的语句无法调用局部times,所以不会污染代码。
function createCounter () {
var times = 0;
return function () {
times ++;
return times;
}
}
var inc = createCounter();
// 接下来不断调用 inc() 看看吧
2
3
4
5
6
7
8
9
闭包用起来简单,实现起来可不容易!有一个值得注意的问题,当闭包的返回函数引用了循环变量之后,可能会出现一些意外。
接下来这个函数并没有什么实际的意义,只是为了举例说明。that函数会返回一个数组,这个数组中存放着三个子函数。每个子函数会返回当前循环变量i的值。
function that () {
var res = [];
for (var i = 1; i <= 3; ++ i) {
res.push(function () {
return "i = " + i;
});
}
return res;
}
var example = that();
example[0](); // 4
example[1](); // 4
example[2](); // 4
2
3
4
5
6
7
8
9
10
11
12
13
14
运行结果全是4!子函数虽然存放在了数组中,但是并没有立即调用。等你调用子函数时,循环已经停止,i的值已经是4了。
解决方案肯定是有的,会稍微有点绕。我们需要一个不变的东西去替代i。那么再搞一个函数吧,把i作为它的参数。
function that () {
var res = [];
for (var i = 1; i <= 3; ++ i) {
res.push((function (n) {
return function () {
return "i = " + n;
}
})(i)); // 注意这里,把i作为参数传入!
}
return res;
}
var example = that();
example[0](); // 1
example[1](); // 2
example[2](); // 3
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
运行试试看!此时问题就解决了。注意这里用了一个“创建一个匿名函数并立刻执行”的语法:
(function (x) {
return x * x;
})(3); // 9
2
3
你已经学会了闭包,和她上个号庆祝一下吧!
# 复述指引
- 当内部函数被保存到外部时,将会生成闭包。
- 可以读取到其他函数内部的变量
- 可以将变量保存在内存中
- 闭包会导致原有作用域链不释放,造成内存泄漏(内存占用)