理解JavaScript的闭包

时间:2022-08-26 10:35:41

理解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每个函数都算是一个闭包)。

闭包作用

  1. 读取函数内部的变量.
  2. 让这些变量的值始终保持在内存中,避免被垃圾回收.

这两个作用其实上面都提到了,但是闭包作用 用不好那就是闭包的最大弊端。
闭包的第一个作用可以在父函数外部,改变父函数内部变量的值,使用不当会破坏父函数的封装性。而闭包的第二个作用如果使用不当就会造成内存泄漏的问题。

由于闭包会使得函数中的变量长时间保存在内存中,内存消耗是很大的,如果滥用闭包,那么会大大降低浏览器的性能。
只要对闭包的引用都被释放了,那么闭包中的*变量自然就会被释放。

总结

闭包就是一个子函数引用一个父函数的变量,因为变量被引用着所以不会被回收,因此可以用来封装一个私有变量,这样就可以在父函数外部通过子函数来操作父函数中的私有变量。这既是优点也是缺点,不必要的闭包会破坏父函数结构而且会徒增内存消耗。