最近在复习ES6的知识点,又重新理解了闭包这个概念,发现自己之前的理解只是“浅尝即止”,今天有了新的理解,特地来总结一波。
首先我们从经典三问来看:是什么(what)?为什么(why)?怎么产生闭包(how)?
一、什么是闭包?
在 JavaScript 中,根据词法作用域的规则,内部函数总是可以访问其外部函数中声明的变量,当通过调用一个外部函数返回一个内部函数后,即使该外部函数已经执行结束了,但是内部函数引用外部函数的变量依然保存在内存中,我们就把这些变量的集合称为闭包。比如外部函数是 foo,那么这些变量的集合就称为 foo 函数的闭包。
那么总结出以下两点:
1、闭包是一个嵌套在函数里面的内部函数所产生的闭合的一个容器,可以理解成一个对象(是以key:value的形存在的)。
2、闭包保存的是内部函数访问外部函数的变量,并且保存在内部函数当中。
二、怎么产生闭包(产生闭包的条件)?
根据前面第一点可以知道,产生闭包首先第一点需要函数嵌套,否则也无谈内部函数外部函数了。 其次,内部函数需要引用了外部函数的变量才能产生闭包。
比如这段代码:
function fun() {
var num = 123;
// 函数嵌套
function fun2() {
// 内部函数引用外部函数的变量
console.log(num);
}
fun2()
}
fun()
从控制台可以看到,闭包是一个对象(键值对的形式),而且保存在嵌套的内部函数(fun2())当中,闭包的作用是保存外部函数的变量(在内部调用的变量)。
三、为什么要设计闭包
其实我们也知道,闭包可以保存函数的变量,那么其实也就是
1、可以延长局部变量的生命周期
2、可以从外部访问函数内部的局部变量
四、闭包的缺点
闭包也有缺点,比如闭包虽然延长了局部变量的生命周期,于此同时带来的也就是,长时间占用内存,这也会导致,如果不及时清除闭包容易使内存溢出。
那么如何避免闭包带来的缺点呢? 就是用完闭包之后及时的清除闭包。
五、闭包的使用场景
1、解决循环遍历加监听的问题。
不知道大家有没有遇到过这样的面试题:
for (var i = 0; i < btns.length; i++) {
var btn = btns[i];
// onclick是事件回调,所以不是立即执行,for循环直接执行。所以等点击的时候i已经变成3了
// 简而言之:for循环是同步操作,而点击事件是异步操作。
btn.onclick = function() {
console.log(i); // 结果:不管点击哪个按钮,都是3
}
}
这样的结果不管点击哪个按钮都是3,但我们的需求并不是这样的,所以我们可以利用闭包解决这个问题。 在onclick事件外嵌套一个立即执行函数,从而形成闭包。 👇
// 解决办法:用闭包解决,当立即执行函数执行完毕之后,作用域销毁,但是点击的时候仍然能够访问到变量i
// 也就是内部变量依然能够访问到
for (var i = 0; i < btns.length; i++) {
var btn = btns[i];
// 立即执行函数
(function(i) {
btn.onclick = function() {
console.log(i); // 结果:点击一次执行一次
}
})(i)
}
2、将内部函数返回出来
function fun() {
var num = 123;
// 函数嵌套
return function fun2() {
// 内部函数引用外部函数的变量
console.log(num);
}
// fun2()
}
// var 一个fun2来接收这个函数
var fun2 = fun()
console.log('----');
fun2(); // 结果:打印出123 ,也就是说在外部也能访问到函数里面本来应该被销毁的变量
3、自定义JS模块
我们可以通过闭包,自定义JS模块,然后在实际使用中可以通过模块暴露的对象,调用方法来实现对应的功能。
结语
最后我想说,其实我认为对于闭包,每个人有每个人不同的理解,也许会有些许的偏差,但是没有绝对的概念。因为每个人的知识体系不同,理解角度不同,思考方式也不同~~
所以我在网上参考资料的时候,是尽量转化成我所能理解并接受的知识,并分享出来,如有错误,欢迎大家批评指正~~