JavaScript-闭包

文章目录


一、理解闭包

  1. 如何产生闭包?
    当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量或函数时,就产生了闭包。
  2. 闭包到底是什么?有两种理解:
    绝大多数人:闭包是嵌套的内部函数
    极少数人:闭包是包含被引用变量或函数的对象
  • 注意:
    (1)闭包存在于嵌套的内部函数中。
    (2)如果外部函数的外部没有对该内部函数的引用,当外部函数执行完毕,内部函数会成为垃圾对象,闭包也会消失。
    (3)闭包中只存在被引用的外部函数的数据(可以是变量或函数)。
  1. 归纳闭包产生的条件:
    (1)函数嵌套。
    (2)内部函数引用外部函数的数据。
    (3)外部函数执行完毕,才会执行内部函数的定义,然后才产生闭包,但并不需要调用内部函数。


二、常见的闭包

  1. 第一种:将内部函数作为外部函数的返回值
    JavaScript-闭包

  2. 第二种:调用函数时,将一个函数作为另一个函数的实参传递
    JavaScript-闭包

三、闭包的作用

  1. 延长变量的生命周期
    使外部函数中定义的局部变量在外部函数执行完毕过后仍然存在与内存中。
  2. 在函数外部操作内部数据
    通过闭包,我们可以在函数外部操作函数内部的变量。
  • 涉及闭包的一些问题
    1.函数执行完毕后,函数内部生命的局部变量是否还存在?
    答:一般情况下,函数内部生命的局部变量会在函数执行完毕后被销毁;但如果存在闭包,那么在闭包中会仍然存在一些被引用的局部变量。(有一个条件:外部函数执行完毕后不能是垃圾对象)
    2.在函数外部能否访问到函数内部的局部变量?
    答:一半情况下外部不能访问函数内部的变量,但存在闭包的情况下,我们可以通过闭包间接操作局部变量。

三、闭包的生命周期

  1. 产生
    闭包在嵌套的内部函数的定义语句执行完毕时产生,这里不需要调用内部函数,但必须调用外部函数。
  2. 死亡
    在含有闭包的内部函数成为垃圾对象时,闭包也会被销毁。(即当外部函数的外部没有对该内部函数的引用时,内部函数成为垃圾对象自动释放内存,闭包也会消失)。

四、闭包的应用:自定义JS模块

JS模块是指具有某些特定功能的 js 文件。

特点:

  1. 将所有的数据(变量或对象)和功能(函数)封装到一个函数内部(私有的)。
  2. 只向外暴露一个对象或函数,该对象或函数包含需要操作的特定对象或方法。
  3. 模块的使用者,只需要通过调用模块暴露给外部的对象来调用方法以实现相应功能。
  4. 调用模块函数前需要先引入模块函数。

自定义JS模块:有两种方式

  • 方式一:以 return 的形式暴露内部函数,内部函数引用外部函数的数据。
    定义模块:
function mymodule(){
    var msg = "my atguigu";
    function doSomething(){
        console.log('doSomething: '+msg.toUpperCase());
    }
    function doOtherthing(){
        console.log('doSomething: '+msg.toLowerCase());
    }
    return{
        doSomething: doSomething,
        doOtherthing: doOtherthing
    }
}
  • 方式二:创建匿名函数自调用并定义全局变量
    定义模块:
(function(){
    var msg ="My atguigu";
    function doSomething(){
        console.log('doSomething: '+msg.toUpperCase());
    }
    function doOtherthing(){
        console.log('doSomething: '+msg.toLowerCase());
    }
    window.myModule2 = {
        doSomething: doSomething,
        doOtherthing: doOtherthing
    }
})();
  • 调用模块
    通过方式一定义的JS模块调用时需要限制性模块函数,通过方式二定义的模块调用时则不需要,两种方式都需要先引入模块文件。
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <!--引入JS模块-->
    <script type="text/javascript" src="./js/myModule.js"></script>
    <script type="text/javascript" src="./js/匿名函数自调用.js"></script>
</head>
<body>
<script>
    //执行模块函数并调用方法
    mymodule().doSomething();
    mymodule().doOtherthing();
    //调用JS模块
    myModule2.doSomething();
    myModule2.doOtherthing();
</script>
</body>
</html>

五、闭包的缺点及解决办法

  • 缺点:
    因为闭包可以延长局部变量的生命周期,一些并不需要的变量也不会被释放,所以会造成内存占用问题。
    容易造成内存泄漏问题。
    -解决:
    1.能不用闭包就尽量不适用闭包
    2.及时释放内存,即手动停止对内部函数的引用,使内部函数成为垃圾对象,进而自动回收闭包。
    如下图:
    JavaScript-闭包

  • 相关概念补充
    (1)内存溢出:一种程序运行时出现的错误,当程序运行需要的内存超过了剩余的内存时,就会抛出内存溢出错误。
    (2)内存泄漏:内存泄漏指的是占用的内存没有及时释放,内存泄漏积累过多就容易导致内存溢出。(内存泄漏时程序还能运行,只是导致可用内存变小)。

  • 常见的容易导致内存泄漏问题的有:
    (1)意外的全局变量。(比如在函数内部定义局部变量时忘记写var,就会默认为是全局变量。)
    (2)没有及时清理的计时器或回调函数。
    (3)闭包。

六、面试题

片段一:没有闭包
JavaScript-闭包
片段二:有闭包
JavaScript-闭包
片段三:
JavaScript-闭包
解题关键:有无产生闭包


(完)

上一篇:详解ES6(四)-函数名与参数


下一篇:React 小技巧:摆脱hooks依赖烦恼