深究 JavaScript 中闭包的概念
闭包是js的一个难点,也是它的一大特色,是我们必须掌握的js高级特性之一,那么什么是闭包呢?它又有什么用呢?在网上翻阅资料后,给我的感觉是,基本上大致都是一个意思,不难理解。那么接下来,博主将使用简洁并且形象的方式给大家来解释。
1、什么是闭包?
术语:闭包(closure)指有权访问另一个函数作用域变量的函数。我们可以理解为,一个作用域可以访问另一个函数作用域的局部变量。例如在全局作用域下访问局部作用域下的变量,又或者两个不同的局部作用域之间进行访问局部变量,在这种情况下我们不得不使用闭包函数。
形象比喻:通俗地讲就是别人家有某个东西,你想拿到但是因为和他家里人不熟,但是你可以和他家的孩子套近乎,通过他家的孩子拿到你想要的东西。
这个家就是局部作用域,而你不是他家的人,所以外部无法访问内部的变量,而他家的孩子是返回对象,对家里的东西有权访问,那么我们就可以借助返回对象间接访问内部的变量。
总结:[函数] 和 [函数内部能访问到的变量](也叫环境)的总和,就是一个闭包。
2、闭包的特点
通过上面形象而又不失节操的比喻,相信大家对闭包都有一定的了解了,那么闭包有什么特点或者闭包适应的环境是什么呢?
- 在函数嵌套中发生
- 内部函数可以引用外部函数的参数或者变量
- 参数和变量不会被垃圾回收机制回收,因为内部函数还在引用
- 函数作为返回值被return返回
- 闭包函数可以作为参数传递
2.1 闭包经典案例
下面我们看一个闭包函数的经典案例,来加固大家对闭包概念的深刻理解,我们使用简单的函数来实现。
// 声明一个普通的函数
function ext(){
// 声明一个局部变量,并赋值为1
var a= 1;
// 返回一个匿名函数
return function(){
// 内部的函数对外部的局部变量进行修改
a++;
// 打印到控制台
console.log(a);
}
}
// 调用ext函数,并返回一个匿名函数,使用变量接收
let inter = ext();
inter(); // 输出2
inter(); // 输出3
// 当然,我们还可以在内部函数中将a变量返回,使得全局变量可以对其进行直接的使用
分析:
一般情况下,函数ext执行完后,就应该连同函数体内部的局部变量一天被回收,但在这个例子中。匿名函数作为ext函数的返回值被赋值给全局作用域下的inter变量,并且匿名函数内部引用着ext函数中的变量a,导致变量a无法被回收,并且调用匿名函数后还进行了累加。那么这就形成了闭包,并且也显示出了闭包的不足,即存在内存消耗的问题。
2.2 计时器经典面试题
for(var i=0;i<5;i++){
setTimeout(function(){
console.log(i)
},100)
}
// 输出5个5
分析:
按照预期,它应该依次打印12345
,但结果却输出了5个5,这是什么原因引起的呢?首先我们要知道js的执行机制,js是单线程的,所以会有异步任务和同步任务,同步任务先执行,而异步任务后执行。而异步任务包含计时器事件,那么也就意味for循环
要比计时器先执行完成,然后再执行计时器任务。所以按照这个逻辑分析,当for循环执行完毕后局部变量i变成了5,然后计数器才执行5次,所以会输出5次5.
但是如果将变量声明关键字var
换成let
则会得到我们预取的效果,因为let会将当前的模块暂时性锁死,所以不会存在 var
变量提升的问题。
注意:其实这个案例最重要的是涉及到了变量声明和变量提升的问题,而并非闭包。
2.3、闭包作为参数传递
var num = 15;
var fn1 = function(x){
if(x>num){
console.log(x);
}
}
void function(fn2){
var num =100;
fn2(30);
}(fn1);
分析:
在这段代码中,函数fn1作为参数传入立即执行函数中,在执行到fn2(30)的时候,30作为参数传入fn1中,这时候if(x>num)中的num取的并不是立即执行函数中的num,而是取创建函数的作用域中的num这里函数创建的作用域是全局作用域下,所以num取的是全局作用域中的值15,即30>15,打印30
3、闭包的优缺点
优点
- 保护函数内的变量安全 ,实现封装,防止变量流入其他环境发生命名冲突
- 在内存中维持一个变量,可以做缓存(但使用多了同时也是一项缺点,消耗内存)
- 匿名自执行函数可以减少内存消耗
缺点
-
被引用的私有变量不能被销毁,增大了内存消耗,造成内存泄漏
解决:可以在使用完变量后手动为它赋值为null;
-
由于闭包涉及跨域访问,所以会导致性能损失
解决:可以通过把跨作用域变量存储在局部变量中,然后直接访问局部变量,来减轻对执行速度的影响
4、总结
闭包总的来说不是太难,只要大家能明白作用域、变量提升、js的执行机制,我相信学习闭包是非常简单并且容易上手的。最后给大家说一下,void是js中的一个操作符,表示该函数没有返回值,或者说返回值为undefined
。