关于JS中闭包剖析(包含闭包的理解、作用、生命周期、应用、缺点及其解决、常见的闭包)

JS中闭包的理解剖析

写在前面:如果此篇博客中有任何错误的地方,欢迎大家的指正!让我们共同进步!
如果觉得这篇博客有用就点赞+收藏+关注三连吧!


这几日对于JS中的闭包进行了相对深入的了解,写在这里,以便于后面复习回顾的时候好看。

关于闭包,大致分为六个内容,分别为
闭包的理解、常见的闭包、闭包的作用、闭包的生命周期、闭包的应用、闭包的缺点及其解决。


一、闭包的理解

闭包是如何产生的?
在这里插入图片描述


闭包到底是什么?
在这里插入图片描述


产生闭包的条件
①函数嵌套
②内部函数引用了外部函数的数据(变量\函数)
③并且调用了外部函数


举例说明何时产生闭包
在这里插入图片描述
让我们在浏览器中调试一下,看看是否是这样。
在这里插入图片描述
注:这里是加了断点才能在fn1()调用过程中看到fn1内部的闭包,但是整个fn1()调用结束以后,闭包就消失了,因为没有对其进行引用保存。(有关这里更深入的解析请看 闭包的作用

相信大家看到这里对闭包有了一定模糊的认识,接下来,让我们来看看常见的闭包。


二、常见的闭包

两种常见的闭包
在这里插入图片描述
①将函数作为了一个函数的返回值

在这里插入图片描述
由于满足在第一部分所说的闭包产生的三个条件,因此这个代码中是产生了闭包的。虽然闭包产生了,但是fn2()中的console.log(a)语句并没有执行。

接下来让我们看一下对 f 进行调用会有什么效果。

在这里插入图片描述
这里的执行结果是他分别打印了3和4。

这说明什么呢?
答:这说明变量a还是能够进行读写操作,即a没有消失

这是什么原因呢?
答:这是由于闭包的一个特点就是函数内部引用外部的变量(如这里的变量a)会一直存在于内存中,不会被立即释放。
而在第一个f()执行完了以后,这个闭包依然存在,第二个f()执行完了以后,也依然存在,直到关闭浏览器。

在此处,外部函数执行几次,就有几个闭包的产生,而和内部函数执行多少次没有关系。因此,截图代码整个过程中只产生了一个闭包,因为fn1()只执行了一次,并将其引用保存到了变量f中,而无论f()执行了多少次,整个过程都只产生了1个闭包。


②将函数作为实参传递给另一个函数调用
在这里插入图片描述
注意:图中圈中的区域才是闭包范围(即只有msg,而没有time)。

区分:若如下图↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓所示,则没有闭包产生了。
在这里插入图片描述

但是这两种闭包虽然这样定义,但是闭包的作用到底是什么呢?
接下来就让我们一起看看。


三、闭包的作用

两个闭包的作用

①使函数内部的变量在函数执行完以后,任然可以存活在内存中(延长了局部变量的生命周期)
②让函数外部可以操作(读写)到函数内部的数据(变量/函数)

理解注意:
②中所谓的读写,是指的通过封装的内部函数进行操作,内部函数封装什么读写操作,就只能执行那些读写操作,而不能在外面重新自定义访问和修改(读写)。


为了让我们更好的理解闭包,我们在上上一个代码案例的基础上进行一些修改。
在这里插入图片描述
执行过程剖析:进入fn1()后,在 ‘return fn3’ 语句之前,这个时候整个环境中存在两个闭包,分别是fn2()的函数体以及fn3()的函数体。
但是在 ‘return fn3’ 语句之后,
①对于fn2()
当31行代码执行完毕之后,fn2()就不在了。
分析:
fn2()也是一个对象,没有人引用它,他就会在fn1()函数调用之后变成垃圾对象了。
换言之,由于没有人引用fn2(),使得fn2()相当于一个局部变量,这个局部变量没有被保存在其他引用中继续使用,因此会在fn1()函数调用后自动释放。

②对于fn3()
当31行代码执行完毕之后,fn3()就不在了,但是他内部的代码内容依旧还在。
分析:
由于在31行代码中,用一个变量指向了这个fn3函数对象,如下图所示,
在这里插入图片描述
这使得在31行代码执行完毕以后,fn3()这个对象虽然不在了,但是由于var f有对这个函数的引用,因此fn3()这个函数内部的内容依旧存在。
也就是说,是 f 对其的引用 导致闭包最终存在,一直没有消失


四、闭包的生命周期

闭包的产生与死亡
在这里插入图片描述
特别注意!不是在内部函数调用的时候产生,而是内部函数定于预解析的时候就会产生闭包!


接下来我们来举一个例子来说明这一过程。
在这里插入图片描述分析:此时是正在执行23行代码的时候,跳到了16行,由于有函数提升,因此这个时候内部函数对象以及创建,因此闭包已经产生。
当一个闭包我们不需要它的时候,就将其引用变量置为null,这样才能使闭包所占的内存空间得以释放。


若以变量的方式声明函数:
在这里插入图片描述
则是在17-20行函数全部执行完了才会产生闭包。


五、闭包的应用

定义自定义的JS模块

  • 具有特定功能的js文件
  • 将所有的数据和功能都封装在一个函数内部(私有的)
  • 只向外暴露一个包含n个方法的对象或函数
  • 模块的使用者,只需要通过模块表露的对象调用方法来实现对应的功能

在这里插入图片描述


①法1:我们首先创建一个.js后缀文件。

function myModule() {
    //私有数据
    var msg = 'I Love SWPU';
    //操作数据的函数
    function doSomething() {
        console.log('doSomething() ' + msg.toUpperCase());
    }
    function doOtherthing() {
        console.log('doOtherthing() ' +msg.toLowerCase());
    }

    //返回的是一个对象
    //向外暴露对象(给外部使用的方法)
    return {
        //字符串:函数名
        doSomething:doSomething,
        doOtherthing:doOtherthing
    }
}

②法1:并在另外一个.html后缀文件中对其进行使用。

<body> 
    <script type="text/javascript" src="myModule.js"></script>
    <script type="text/javascript">
        var module = myModule();
        module.doSomething();
        module.doOtherthing();
    </script>
</body>

另外一个更加简洁的定义模块方式(不需要在对这个模块进行声明,而是直接使用)——通过匿名函数自调用,将这个模块直接添加为window的属性

①法2:我们首先创建一个.js后缀文件。

(function(){
   //私有数据
   var msg = 'I Love SWPU';
   //操作数据的函数
   function doSomething() {
       console.log('doSomething() ' + msg.toUpperCase());
   }
   function doOtherthing() {
       console.log('doOtherthing() ' +msg.toLowerCase());
   } 
   //把要暴露的东西添加为window的属性
   window.myModule2={
       doSomething:doSomething,
       doOtherthing:doOtherthing
   }
})()

②法2:并在另外一个.html后缀文件中对其进行使用。

<body>
    <script type="text/javascript" src="myModule2.js"></script>
    <script>
        myModule2.doSomething();
        myModule2.doOtherthing();
    </script>
</body>

针对于第二种模块添加,这里分享一个题外小技巧(提高执行效率)。

//一种更普遍的写法,会有代码压缩
(function(window){
    //私有数据
    var msg = 'I Love SWPU';
    //操作数据的函数
    function doSomething() {
        console.log('doSomething() ' + msg.toUpperCase());
    }
    function doOtherthing() {
        console.log('doOtherthing() ' +msg.toLowerCase());
    } 
    //把要暴露的东西添加为window的属性
    window.myModule2={
        doSomething:doSomething,
        doOtherthing:doOtherthing
    }
 })(window)

六、闭包的缺点及其解决

闭包虽然 使得函数内部变量在函数执行完以后,任然可以存活在内存中(延长了局部变量的生命周期),也让程序员在 函数外部可以操作(读写)到函数内部的数据(变量/函数)。但是他也带来了一些缺点。
在这里插入图片描述
若对内存泄漏这个名字有一定疑惑,可以看一下我的另外一篇博客。

为了解决这个问题。
我们可以通过两个方式:
①能不使用闭包就不使用
②及时释放闭包

显而易见的是,第一种方式基本是无法避免的,不然也就不会有闭包的产生了,因此我们举个例子说明第二种方法——及时释放。

在这里插入图片描述
如果觉得这篇博客就点赞+评论+收藏吧!给予小白程序员一些鼓励!


  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值