js 闭包解析-由浅入深(精讲原理)

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

这里涉及了varletconst的知识,这块的知识在这我不做过多的介绍,有兴趣的朋友可以去看看阮一峰老师的开源作品 ECMAscript 6 入门, 传送门点击跳转 里面关于letconst教程很详细

让我们回归正题
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)》

        

结尾

希望大家看了本篇文章都有收获 …
如果文章有什么不对的地方,请斧正,感谢大家
新手一个,大佬勿喷,感谢

        

转载请注明出处,再次感谢
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值