在JavaScript中学习函数时,我们有时候会在函数内部去访问该函数外部的变量,这个函数就叫做闭包(Closure)。简短的解释就是,闭包就是有权访问另一个函数作用域中变量的函数。MDN中是这样对闭包进行解释的:闭包是函数和声明该函数的词法环境的组合。就是说闭包由两部分组成,一种是函数本身,另一个是创建该函数的环境。而这个环境包含了这个闭包创建时所能访问的所有局部变量。并且,有个特殊情况是,一个函数内部访问全局变量,这个函数也是闭包。
说了这么多,可能你看了有点晕,我们直接通过代码来解释:
//局部变量的累加
function cum(){
var count = 0;
count++;
console.log(count);
}
cum();//1
cum();//1
观察上面的代码,结果都是 1
,其工作原理是什么呢?
那是因为一般函数执行完毕之后,其内部的局部变量就会跟这个函数作用域一起被销毁,所以这里每执行一次 cum
,count
都会重新累加,因为它 cum
这个函数执行一次就会被销毁,包括内部的 count
变量。这种情况是没有产生闭包的,因为我们还没有对另一个函数作用域中变量进行访问呢!
那么接下来看两个是闭包的案例。
- 案例1 — 函数内部访问全部变量
//全局变量的累加
var count = 0;
function cum(){
count++;
console.log(count);
}
cum();//1
cum();//2
观察上面的代码,这次输出的结果就有了累加的效果了。其工作原理是什么呢?
那是因为全局作用域中的变量在页面关闭之前是会一直存在的,闭包也是一样,会长期储存在内存中,除非你把这个页面关了。所以每次执行 cum
的时候,count
就会自增 1
,因为 count
是一直保存在内存中没有被销毁的。
所以,函数 cum
每次执行时都会访问全局变量 count
,而 count
一直都存在在内存中,每次自增,其值就会加1,所以结果是 1
和 2
。
- 案例2 —内部函数访问外部函数的局部变量
//局部变量的累加
function test(){
var count = 0;
return function(){
count++;
console.log(count);
}
}
var result = test();
result();//1
result();//2
观察上面的代码,我们可以看到函数 test
内部嵌套了一个匿名函数,而这个匿名函数可以访问其外部的局部变量,就是这个匿名函数可以访问函数 test
的局部变量 count
,所以此时这个匿名函数称为闭包。并且,由于是闭包在内存中存在的(除非页面关闭),其内部访问到的变量此时并不会随着外部函数作用域的销毁而销毁,而会跟闭包一样,保存在内存中。你要这么想,你函数执行完就把变量 count
给拿走了,我这个匿名函数还怎么用 count
呀,是吧?!所以 count
会一直存在在内存中,每次自增1,count
值就会加1,所以结果是 1
和 2
。
看完上面两个关于闭包的案例,有的人可能疑惑了。说闭包到底有什么作用?
在我们的日常开发中,如果频繁的使用全局变量的话,会对浏览器的内存给予很大压力,会大大降低其性能,因为全局变量一旦创建,是会一直保存在内存中的,直到你把这个页面给关闭了。这还只是一方面,如果全局变量遭到了修改,那我们这个函数也会受到影响。闭包是一种保护私有变量的机制,在函数执行时形成私有的作用域,保护里面的私有变量不受外界干扰。直观的说就是形成一个不销毁的栈环境。所以说,保护私有变量才是闭包的最大优点。
万物都不是完美的,有利又有弊,闭包也有它的缺点,就是常驻内存,会增大内存的使用量,使用不当会造成内存泄露。
什么是内存泄漏呢?
闭包会引用包含函数的整个变量对象,如果闭包的作用域链中保存着一个HTML元素,那么就意味着该元素无法被销毁。所以我们有必要在对这个元素操作完之后主动销毁。
简单来说,我们使用了闭包后,如果是闭包用过的变量,我们后面要销毁这个变量就要单独处理,这样我们这个单独的操作就会告诉别人我们函数内部使用了什么变量,这就是内存泄漏。
- 总结闭包的优缺点:
优点:可以把局部变量驻留在内存中,可以避免使用全局变量;保护私有变量不受外界干扰。
缺点:常驻内存会增大内存使用量,并且使用不当很容易造成内存泄露。
所以在日常开发中,我们要使用闭包的时候一定要注意了,如果不是因为某些特殊任务而需要闭包,在没有必要的情况下,在其它函数中创建函数是不明智的,因为闭包对脚本性能具有负面影响,包括处理速度和内存消耗。