多么痛的领悟之闭包小结

“闭包”,一个快被讲烂了的知识点,以至坊间流传着一种说法,不识闭包的人就不能谈精通js。好吧,虽然现在大部分时间在写node后端,极少用到闭包,但是为了以后写前端js真正遇到闭包特性时,不必乍慌~

关于什么是闭包的定义,官网上的解释有点晦涩,参考了网上一大圈解释,选了几个易懂的方便理解:第一个,
闭包就是能够读取其他函数内部变量的函数。第二个,可能要长点:2.1闭包就是函数的局部变量集合,只是这些局部变量在函数返回后会继续存在;2.2闭包就是就是函数的“堆栈”在函数返回后并不释放,我们也可以理解为这些函数堆栈并不在栈上分配而是在堆上分配;2.3当在一个函数内定义另外一个函数就会产生闭包。

那什么时候的场景会用到闭包这样的特性呢?一个最常见的例子,你如果想访问函数内部的变量该怎么办?

1
2
3
4
5
6
7
function func2(){
var num1 = 22;
num2 = 33;
}
func2();
console.log(num1);//undefined
console.log(num2);//33

像上面的这个例子:你在外面直接输出num2会报“没有定义”的错,这不难理解,因为函数作用域的缘故。那,我们有没有办法可以访问其内部变量呢?按以前静态语言的做法,就是再封装一个public的方法,里面返回局部私有变量,在js中呢,我们的闭包就该登场了,将上面的代码改造下:

1
2
3
4
5
6
7
8
9
10
 function func3(){
var num3 = 44;
function func4(){
return num3;
}
return func4;
}
var func = func3();
console.log(func());
}

再来分析,虽然在外面我们不能直接取到num3,但是func4可以取到num3,因此返回一个func4的引用,这样在外部通过这个func4就可以获取到func3的内部变量。记住,闭包中局部变量是引用而非拷贝。其实兜这么大一圈去取得num3的这种方法,感觉真是跟静态语言想在外部访问类的私有变量是异曲同工呀。这里的func4就是静态语言的类的公有方法,只不过在js里面就变成了闭包!或许这就是当初设计js时没有类和对象,但是又想实现如上的操作而制作了这样的一个‘坑’。

再举一个有时候会在工作中遇到的情景吧,也是网上的例子。在一个循环里面会异步执行操作,如果不做处理,出来的结果一般会让你大跌眼镜。这是使用闭包的一个坑。

1
2
3
4
5
6
7
8
9
10
//原始例子,定时服务也可以看作是一个异步操作
function f1(){
var i = 0;
for(i = 0; i < 10; i++) {
setTimeout(function() {
alert(i);
}, 1000);
}
}
f1();

上面的代码我们本来的意图是通过定时函数执行f1内部的闭包依次来输出f1的局部变量i,但是结果却是输出的10个10而不是期望的0到9,因为闭包内是对i的引用,然后函数执行时i已经变成了10。所以,要解决这个问题,我们可以进行如下的修正:

1
2
3
4
5
6
7
8
9
10
11
//原始例子,定时服务也可以看作是一个异步操作
function f2(){
for(var i = 0; i < 10; i++) {
(function(e) {
setTimeout(function() {
alert(e);
}, 1000);
})(i);
}
}
f2();

这里的匿名函数将i作为参数,这里的e会有i的一个拷贝,而引用时是对e的引用,这就避免了上述的问题。

另外还有一个关于闭包的场景描述可以看看~