闭包 : 内部函数使用了外部函数的变量 , 那么内部函数和这个变量之间的环境就叫闭包
闭包的特点 , 在闭包里保存了外部函数的变量,即使函数死亡了,只要闭包还在,那么变量就一直还活跃在内存中
应用1 : for循环问题
在js中var定义的变量没有块级作用域一说, 只有局部和全局变量
for循环里是一个{}块级作用域
用var定义时就成了全局变量
var btn=document.getElementsByTagName('button')
for (var i = 0; i < 5; i++) {
btn[i].onclick=function () {
alert(i)
}
}
上面这段代码每次执行for循环的时候,对应的给每个按钮绑定了一个函数, 但是函数的内容是alert(i), 绑定的时候并没有执行函数 , 当回调函数执行的时候, for循环就已经执行完了 , 每个按钮都已经绑定上了函数, 而i只有一个 每次循环就++;
当for循环执行完的时候 , i就已经等=4了
所以当onclick回调函数执行时 , alert的i就是全局的那个i(4)
使用闭包
for (var i = 0; i < 5; i++) {
(function (j) {
btn[j].onclick=function () {
alert(j)
}
})(i)
}
在for循环里定义一个立即执行函数, 每次循环就会执行一次这个函数, 在这个函数里定义Dom回调函数 , 那么这个匿名函数就回调函数的外部函数 ,
在匿名函数里参数里声明一个变量(参数里的变量即在函数体力var一个变量) , js都是值传递, 那么传递参数的时候就可以将每次for循环的值传进这个匿名函数里 , 在回调函数里使用外部函数的变量 , 就产生了一个闭包 , 每次执行for由于dom对象的不同, 那么就每次都生成一个闭包 , 这个闭包和函数一起绑定给了dom的回调函数 , 闭包里保存的每次执行时的外部函数(匿名函数)的变量, 即每一次的i值
那么当dom回调函数执行的时候, alert会在当前作用域里寻找这个i 恰好闭包里保存了这个i 所以执行的时候就可以正常输出了
总结: 利用闭包将变量的生存周期延长 , 并封闭在闭包里 , 达到了模仿块级作用域的效果
上面匿名函数产生的闭包可以在dom元素绑定的onclick事件的回调函数里保存
使用let,const
es6中的变量let和const都是支持块级作用域的
使用let定义i时,每一次for循环执行时都是一个新的块级作用域, 这里有5个i , 分别在不同的区块中 , 因此每次dom元素的onclick被触发时, 就直接到它绑定的回调函数所在的区块中去寻找这个i
补充:
js函数中的scopes是函数在定义时开辟的作用域给赋值的 , 里面说明了函数所能访问到的作用域和对应的作用域里的数据
到函数执行的时候, 就会在这里面找它所需要的数据
应用2:封装
js不同于java对象拥有私有变量的 , 但是js可以通过闭包来实现变量私有化
上面应该也提到过 , 在一个js函数中, 把关键数据都定义在外部, 在内部函数里只提供有限的操作 , 以达到封装的目的 , 因此就有了模块化
function myModule() {
//私有数据
var msg = 'My atguigu'
//操作数据的函数
function doSomething() {
console.log('doSomething() '+msg.toUpperCase())
}
function doOtherthing () {
console.log('doOtherthing() '+msg.toLowerCase())
}
//向外暴露对象(给外部使用的方法)
return {
doSomething: doSomething,
doOtherthing: doOtherthing
}
}
上面一个js模块 , 完成了特定功能 ,只向外暴露了想要给你操作方法 , 执行这个函数时将暴露给的函数接收即可 , 之前应该说过外部函数里的变量全部死亡了 ,但是内部函数指向的那个对象重新被接收了 , 因此可以一直保持活跃
除了上述的暴露方法之外, 还有一种暴露方法更加简洁
(function (window) {
//私有数据
var msg = 'My atguigu'
//操作数据的函数
function doSomething() {
console.log('doSomething() '+msg.toUpperCase())
}
function doOtherthing () {
console.log('doOtherthing() '+msg.toLowerCase())
}
//向外暴露对象(给外部使用的方法)
window.myModule2 = {
doSomething: doSomething,
doOtherthing: doOtherthing
}
})(window)
使用立即执行函数, 引入这个js文件时就立即执行了这个函数
该函数将需要暴露的对象给了window属性, 因此引入这个函数之后不需要执行也不需要接收, 直接就像使用全局变量一样使用即可
上面一般在立即执行函数里添加一个widow参数 , 立即执行函数接收并作为全局对象来使用
因为后续压缩代码的时候, 会将里面的参数用a,b等代替, 可能会引起不必要的麻烦
补充:
当想要使用闭包封装的作用时 , 必须要暴露 , 不然封装就没有意义 外部函数执行了闭包产生后就消失了 完全没必要也没任何意义
闭包缺点
函数执行完之后如果不手动释放闭包所在的函数的话 , 那么就会一直保存在内存里 容易造成内存泄漏