1.什么是闭包
闭包
是js语言的一大特色,也是一个难点,有很多的高级应用都需要依靠闭包来实现,同时闭包也是我们必须掌握的js高级特性
这里我尽可能的用比较通俗易懂的话来讲解
注意
我个人的理解是:普通js函数如果执行完毕,那么在内存中,由于浏览器的垃圾回收集机制 (传送门,点击即可),这块内存会被释放回收。但是闭包就不一样了,由于闭包需要return
出去一个函数,这个被return
出去的函数(即子函数),将会在父函数的外部执行,而且这个被return
出去的子函数里面。还有引用父函数内部的变量,此时父函数执行完毕被垃圾回收机制回收了,在外部的子函数有可能是刚刚执行,
此时有趣的问题就来了,如果父函数在内存里面被回收了,那么子函数怎么办,子函数可是引用着父函数里面的变量的,诸位不妨想一想
闭包产生的过程
举个栗子,如果父函数在内存中被回收了,子函数的引用就是一个空,浏览器会直接抛出错误,但是人家js偏偏不想让你抛出错误,还想让你执行成功,所以此时,就产生了一个闭包
父函数执行完毕,把子函数
return
出去了,子函数在父函数外部的某一时刻执行,此时在子函数执行之前,父函数已经执行完毕,垃圾回收开始运转,开始回收父函数所在的内存,但是发现了父函数内部还有变量在别的地方进行着引用,所以这个父函数内的变量在内存中就被保留了下来,所以这也是闭包的一个缺点就是被引用的私有变量不能被销毁,增大了内存消耗,造成内存泄漏,解决方法是可以在使用完变量后手动为它赋值为null;
如果有不对的地方,还请大佬斧正,勿喷
上面这段话还需细细品味,如果有不懂的地方,欢迎来评论,或者看完这篇文章在回过头来看这段话,下面我们来继续看
要想实现闭包需要满足三大条件:
1.父子函数的嵌套关系(即函数里面套一个函数)
2.父函数把子函数return出去
3.子函数内引用父函数内部的变量
这样就是一个最基本的闭包
示例1:
function aaa() {
let a = 0;
return function () {
alert(a++);
};
}
var fun = aaa();
fun(); //1
2.js特殊的变量作用域
要想理解闭包,就要先理解js特殊的变量作用域
1.在函数内部可以直接读取全局变量,反之 在函数外部不可以读取函数内的变量
示例2:内部->外部(在内部读取外部的值)
let fn1 = 132
function fn() {
console.log(fn1)
}
fn() //132
示例3:外部->内部(在外部读取内部的值)
function fn() {
var fn1 = 132
}
fn()
console.log(fn1) // fn1 is not defined
强调:这里需要注意一个点,就是在函数内部声明变量的时候,一定要在
变量前面 声明表示符 否则创建的就是一个全局变量(在任何地方都可以
访问到的变量)
示例4:全局变量
function fn() {
fn1 = 132
}
fn()
console.log(fn1) //132
2.如何从外部读取函数内部的变量
还记得我们刚开始的示例1吗
下面我们来修改一下这个函数
示例5:
function aaa() {
let a = 1;
return function () {
console.log(a)
};
}
var fun = aaa();
fun(); //1
// 在这里我们已经达到了我们的目的,在函数外部读取函数内部的变量,
同时也生成了一个闭包
3.闭包的优缺点
优点
① 保护函数内的变量安全 ,实现封装,防止变量流入其他环境发生命名冲突
② 在内存中维持一个变量,可以做缓存(但使用多了同时也是一项缺点,消耗内存)
③ 匿名自执行函数可以减少内存消耗
缺点
① 被引用的私有变量不能被销毁,增大了内存消耗,造成内存泄漏,解决方法是可以在使用完变量后手动为它赋值为null;
② 其次由于闭包涉及跨域访问,所以会导致性能损失,我们可以通过把跨作用域变量存储在局部变量中,然后直接访问局部变量,来减轻对执行速度的影响
下面来看一下闭包的典型例子
仔细想一下为什么会是5个5
这里涉及了
var
,let
,const
的知识,这块的知识在这我不做过多的介绍,有兴趣的朋友可以去看看阮一峰老师的开源作品 ECMAscript 6 入门, 传送门点击跳转 里面关于let
,const
教程很详细
让我们回归正题
for循环 浏览器内速度很快很快,可能只有几微秒,几微秒就把这5次循环完了,我们设置的定时器是0.1秒执行一次,由于var
的原因,这个for循环的i
是一个全局变量,所以当for循环执行完毕,不管循环执行几次,这个全局变量i
已经被修改成最后一次循环之后的值了,等定时器设置的时间一到,开始执行console.log()
,执行5次(因为循环了5次),但是这5次,都是输出的是console.log(i)
,即我们最后一次for循环的值,所以输出5个5,这就是闭包的一个典型的例子
解决办法
注意:
第一次执行输出nudefined是因为里面的函数是一个匿名立即执行函数,立即执行函数顾名思义就是定义即执行感兴趣的朋友可以去看一下立即执行函数的概念
经过我们的改造后,控制台的打印符合我们的预期
有小伙伴该问为什么了,为什么这样写就能正确打印呢
因为函数有独立作用域呀
,
用我自己的话就是说
可以把函数想象成一个气泡,循环了5个,就是五个气泡,每个气泡对别的气泡来说都是完全独立的隔绝的,所以五个气泡里面的i
不会相互污染(也就是有五个i
),最终输出我们期望的值01234
关于第一个循环也可以这样想象,只不过他们最终输出的 i 是同一个 i 罢了
其实还有一种简单的写法,熟悉 let 的朋友们都知道, let 有块级作用域
let
就是这么优秀, 所以各位小伙伴,能用let
就尽量用let
把
参考
阮一峰老师的 《学习Javascript闭包(Closure)》
结尾
希望大家看了本篇文章都有收获 …
如果文章有什么不对的地方,请斧正,感谢大家
新手一个,大佬勿喷,感谢