//前言
闭包,这个概念对于每位JSer而言都不陌生,它几乎伴随着每个前端入门者的初学阶段,重要到几乎每家公司面试都会问。
关于闭包究竟是什么,闭包干嘛用的,网上各种回答也是五花八门,动不动就扯到隐匿变量/内存泄漏这些概念,让没有C基础的初学者越看越晕,我不能说那些是错的,不过显然对新手不太友好。曾几何时我也是被那些个故作高深的概念绕得七荤八素云里雾里,那今天这篇文章以一个简单到80岁老太都看得懂的demo,来阐明闭包的本质及作用
//变量(内存)销毁
看过红宝书的都见过这个词,不过Nicholas大神的例子我觉得还是有点晦涩,我来举个更简易的例子——现在有一个button和一个有数字的span,button负责让数字加1,代码如下:
#例子1
html
<body>
<button id="add">加1</button>
<span id="span">10</span>
</body>
js
<script>
var a = 10;
add.onclick = function (){
a++;
span.innerHTML = a;
}
</script>
简单到不能再简单了吧,我每次按add按钮,就会让数字加一,现在把js部分做个改变:
#例子2
<script>
add.onclick = function (){
var a = 10;
a++;
span.innerHTML = a;
}
</script>
红宝书告诉我们,没有用的局部变量就会被销毁内存,一开始我百思不得其解(没办法,没有C基础嘛),后来通过这个点击的例子就闹明白了——在你每次按好按钮,函数执行完毕后,你的这个a就被当成没有用的变量被回收了,或者换句话说,内存被销毁了!所以你的a始终都是初始的那个10!而在例子1的js代码里,a是全局变量,全局变量只有当你关闭页面or浏览器的时候才会被销毁!所以a仿佛就有了存储功能,能够记录每次变化后的值,你点或不点,a就在那乖乖的,保持着上一次点击好++的那个值。
好,我相信你看到这里应该明白回收变量(这个词在js里其实就等价于销毁内存)是什么个意思了,那这和我们今天主题“闭包”有什么关系?别急,看代码,我给你在例子1上做点小改动,你就看明白了!
#例子3
<script>
(function (){
var a = 10;
add.onclick = function (){
a++;
span.innerHTML = a;
}
})()
</script>
我在例子1的外面加了自执行匿名函数包裹了一下,现在你就可以把这段操作看成是 函数包裹函数的形式了,放心 ,代码一样能跑!现在我们把外面的匿名函数成为father函数,把里面点击按钮的函数称为child函数,没毛病吧!毕竟包裹关系,好, 现在我就可以说,这就是个闭包环境了,为什么?因为child函数引用了father函数的变量a啊!闭包的最大特性就是,如果里函数引用(or访问,这俩词在这个语境下是等价的)了外函数的某个变量,那这个变量就能享受和全局变量一样的特权,丫不会被回收!除非你关闭页面or浏览器!这也就是为什么a能一直++并正常显示的问题了,因为他被child函数一直访问着,不会被销毁!不会被销毁!不会被销毁!重要的事情说三遍!闭包就这个作用!
好的,那顺着思路继续走,那内存泄漏是什么鬼?很简单,如果你像这种享受全局变量不会被销毁的特权的闭包变量多到一定数量了,那内存就要撑爆了,毕竟咱的电脑对待浏览器是很抠门的,内存就分配给你那么点,一多就会爆,这就是内存泄漏,并不是什么高大上的概念!
//结束语
相信看到这里你应该能够大致明白闭包,以及内存销毁的大致概念了吧~其实我对外面那些讲解闭包的文章最不满意的一点就是,各种外函数return里函数,这种写法会给新手们的理解制造很大的障碍,毕竟return来return去,要调用的时候还要再加一对(),实在反人类,并且最大的问题在于:看多了return的例子,新手们很可能会误认为只有外函数return里函数才是闭包!压根不是这样的!闭包跟return是既不充分也不必要的关系!(虽然实际业务中我们经常要return个里函数来达到强行闭包的效果),并且,闭包和自执行匿名函数也是既不充分也不必要的关系,也不要因为我这个例子就有这种想法,return里函数也好,自执行也好,都只是为了方便理解而已,你甚至可以把全局作用域当成一个最外层的自执行匿名函数的局部环境,就像例子3这样,或者在所有代码外面都包裹上window.onload = function (){}这样看的话,所有引用到最外层变量的函数,就都是闭包了!感谢阅读!