理解JavaScript的闭包
要说到JavaScript最蛋疼的地方,莫过于闭包问题了。对于网上的很多关于闭包的文章,看了还不如不看,越看越乱,在网上徘徊许久,直到看到茄果的一篇关于JS闭包问题的理解,我才顿悟。
变量作用域
首先,要理解闭包,那肯定得先知道变量的作用域是个什么东东。
变量的作用域也就两种:全局变量和局部变量。(废话!)
JS特点:函数内部可以直接读取全局变量,但是在函数外部无法读取函数内部的局部变量。(强封装性)
这里有个小问题:在JS函数内部声明局部变量的时候,一定要使用var。如果不用的话,你实际上声明的是一个全局变量。(很重要)
由于js的强封装性,我们不能直接获取一个函数a内部的变量。d但要想获取怎么办?
那我们就需要在函数a中声明一个内部的函数b,由于b在a内部所以a内的变量对b是可见的,我们只要在函数b中获取变量值,然后将函数b作为返回值返回就可以了,这其实就是通过闭包实现的。
那么就引出这篇文章的重点了——闭包。
何为闭包?
闭包:有权访问 另一个函数作用域内变量 的函数都是闭包。
举个栗子:
function a(){
var n = 0;
function inc() {
n++;
console.log(n);
}
inc();
inc();
}
a(); //控制台输出1,再输出2
这个都能明白。再来一个:
function a(){
var n = 0;
function abc(){
n++;
console.log(n);
}
return abc;
}
var c = a();
c(); //控制台输出1
c(); //控制台输出2
如果你以为输出2个1,那么你确实是不清楚变量的回收机制以及闭包的原理。
这个其实也很简单:
var c = a(),这一句 a()返回值是函数 abc,所以这句等同于 var c = abc;
c(); 这一句等同于abc();
注意:c只是一个指向函数的指针,而()才是执行函数。
因此后面三句实际上就等同于: var c = abc; abc(); abc();
这个栗子里 abc函数访问了构造函数 a 里面的变量 n,即符合上面标题给出的定义,所以abc函数就是一个闭包。
闭包对*变量的保持和释放
什么是*变量?
*变量 不是在闭包内或者全局上下文中定义,而是在代码块的环境中定义的局部变量。
上面栗子里的变量n就是*变量
对*变量的保持:
再看一个栗子:
function createFuncs(){
var res = new Array();
for (var i=0; i < 10; i++){
res[i] = function(){
return i;
};
}
return res;
}
var funcs = createFuncs();
for (var i=0; i < funcs.length; i++){
console.log(funcs[i]());
}
不要以为输出 0~9 ,实际上输出了10个10.
再次强调一个我们经常忽略的重点:函数名+()才是执行函数
所以 var funcs = createFuncs(); 执行的结果是:
var res = new Array(), i=0;
res[0] = function(){ return i; };
i=1;
res[1] = function(){ return i; };
...
res[9] = function(){ return i; };
i = 10;
funcs = res;
res = null;
因为res只是一个指向函数的指针集合,没有(),所以他并没有真正调用闭包function(){ return i; }
等执行到下面的funcs[ i ]()的时候,注意此时有(),这表示对闭包函数的调用,所以function(){ return i; }执行返回10,由于for循环所以返回10次10.
何时会释放*变量?
对于为什么只垃圾回收了res,而 i 却没有被回收?
这是因为result处于createFuncs函数的作用域内,当var funcs = createFuncs() 执行过后,由于result变量超出了作用域,所以被回收。
由于funcs接受了返回值为闭包的函数对象,闭包中包含对i的引用,所以funcs存在,闭包就存在,所以i在内存中就不会被释放。
只有在包含闭包的funcs被释放后变量i才会被释放。
js的函数有很强的封装性,它可以获取外界信息,但是外界却无法直接看到里面的内容(JS每个函数都算是一个闭包)。
闭包作用
- 读取函数内部的变量.
- 让这些变量的值始终保持在内存中,避免被垃圾回收.
这两个作用其实上面都提到了,但是闭包作用 用不好那就是闭包的最大弊端。
闭包的第一个作用可以在父函数外部,改变父函数内部变量的值,使用不当会破坏父函数的封装性。而闭包的第二个作用如果使用不当就会造成内存泄漏的问题。
由于闭包会使得函数中的变量长时间保存在内存中,内存消耗是很大的,如果滥用闭包,那么会大大降低浏览器的性能。
只要对闭包的引用都被释放了,那么闭包中的*变量自然就会被释放。
总结
闭包就是一个子函数引用一个父函数的变量,因为变量被引用着所以不会被回收,因此可以用来封装一个私有变量,这样就可以在父函数外部通过子函数来操作父函数中的私有变量。这既是优点也是缺点,不必要的闭包会破坏父函数结构而且会徒增内存消耗。