对 闭包函数 概念的梳理

131 阅读5分钟

1. 永不销毁的执行空间

   1. 正常书写一个函数
   2. 在这函数内 向外返回一个 引用(复杂)数据类型,并且函数外部,有东西一直引用着这个函数的返回值。
      则这个函数的执行空间不会销毁
      
案例1
  function fn() {
    const num = 100;
    const obj = {
      name:'jack'
    }
    return obj;// 返回一个引用数据类型
  }
  let res = fn();
  /* 
    fn 函数调用,根据fn中的地址,找到函数的存储空间 `0x001`
      在运行内存中 开辟了一个 fn函数的执行空间 `0x00f`
        在函数的执行空间 `0x00f` 中 定义了变量num并赋值为100
        定义了一个对象: 在这个执行空间内存中会开辟一个对象存储空间 地址为 `0x002`
                      并且将对象的数据存储在 存储空间`0x002`中
                      将这个对象的存储空间地址 `0x002` 赋值给变量obj
        执行到return,则将对象的存储空间地址 `0x002`作为函数的返回值 返回
    在fn函数外部,有一个变量引用着函数fn的返回值 `0x002`

    res变量中的值为 地址 `0x002`,通过这个地址一直指向这个对象的存储空间

    因为函数外部,有变量一直引用着函数内的对象,所以函数的执行空间不会销毁
  */
案例2
    <script>
        function fn() {
            // var a = 100
            const obj = {
                name: 'fn函数的name',
                age: '不知道'
            }

            return obj
        }

        // 变量 newObj 内部保存着 fn 函数中声明的一个对象 obj 的地址, 所以 fn 函数就不会被销毁掉, 如果销毁了, 那么对象也就无法访问了
        const newObj = fn()
        console.log(newObj)

        // 将 newObj 的值修改后就与函数内部的对象切断了联系, 那么这个函数的执行空间就会被销毁
        // newObj = null
    </script>

2. 闭包函数

  - 作用域嵌套形成的一种js高级应用场景

1) 闭包形成条件

    1. 大函数执行空间不销毁
    2. 大函数内返回的引用数据类型是一个小函数(函数外部有东西引用着函数内的小函数就行)
    3. 小函数引用了大函数中的数据(变量,形参)
    + 小函数叫做大函数的闭包函数

2) 闭包作用:

  1. 保护变量私有化-----定义在函数内的变量就是私有变量
  2. 可以让函数外部访问到函数内部私有变量的值----通过闭包函数访问
  3. 延长了变量的生命周期----作用域
缺点: 不会销毁的执行空间,多了容易造成内存泄露   

闭包案例1


   function fn() { // 大函数
     var num = 100;
     return function ff() { // 大函数中返回一个小函数
       console.log( num )
       return num; // 小函数引用了大函数中的数据
     }
   }
   // 函数外部有东西一直引用着fn函数的返回值
   let f = fn();
   f() // 100
   
  分析
  1)fn函数调动,根据fn中的地址,找到fn函数存储空间 `0x001`
      在运行内存中 开辟了一个 fn函数的执行空间 `0x00f`
       在函数的执行空间 `0x00f` 中 定义了变量num并赋值为100
        定义了一个小函数ff: 在这个执行空间内存中会开辟一个小函数存储空间 地址为 `0x002`
                      并且将小函数中的代码存储在 存储空间`0x002`中
                      将这个小函数的存储空间地址 `0x002` 作为函数的返回值 返回
    在fn函数外部,有一个变量引用着函数fn的返回值 大函数内小函数的存储空间地址 `0x002`
    所以fn函数的执行空间 `0x00f` 不会销毁

    2f()调用执行:
      f中存储的是地址 `0x002`,
      f() 会在内存中 开辟一个小函数的执行空间 `0x01f`
        在执行空间 `0x01f`中 输出num的值,但是在小函数中没有变量num,
        则会去上一级空间中查找,在fn的执行空间中找到了变量num,所以输出100
        并且小函数 返回100
     小函数中的代码执行结束,小函数执行空间 `0x01f` 销毁  

闭包案例2

  function ff(i) {
    return function (n) {
      console.log( n+ --i )
    }
  }
  let f1 = ff(2);

  f1(3)    // 4
  ff(4)(5) // 8
  ff(6)(7) // 12
  f1(8)    // 8

闭包案例3

<script>
    function fun(n, o) {
        console.log(o);

        const obj = {
            fun: function (m) {
                return fun(m, n);
            },
        };
        return obj;
    }
    var a = fun(0); // undefined
    a.fun(1);   // 0
    a.fun(2);   // 0
    a.fun(3);   // 0
</script>

    /**
     *  var a = fun(0)
     *      调用全局函数 fun (QF001), 并传递了一个实参 0,   所以对应的两个形参 n === 0; o === undefined
     * 
     *          函数内部代码开始执行
     *          1. console.log(o)    undefined
     *          2. 创建一个 对象 obj, 内部有一个属性名为 fun 值为一个函数
     *          3. 将这个 对象 obj 返回到函数外边   (变量 a 接受了 这个 对象, 所以 将来 变量a 可以调用内部的 fun)
     *  
     
     *  a.fun(1)
     *   调用的是 对象 obj 内部的 fun (QF999) 函数, 并传递了一个 实参 1, 所以对应的一个形参 m === 1
     *   开始执行函数内部代码
     *   return fun(m, n)    注意!!! 这个函数是全局函数 fun(QF001). 如果想要调用对象内部的 应该是  对象.属性名()
     *                  并传递了两个实参
     *                      m === 1
     *                      n === 0     (函数QF999 的作用域中没有, 所以需要去上一层作用域查找, 也就是 函数QF001,
                        在这个函数中找到了一个形参n, 它的值为0)
     *                  函数开始执行时就会执行一句 console.log(o)   我们传递的是数字 0
     *  
     
     *  a.fun(2)
     *      调用的是 对象 obj 内部的 fun(QF999), 并传递一个 实参 2, 所以对应的一个形参 m === 2
     *          开始执行函数内部代码
     *              return fun(m, n)    传递的两个实参  m === 2;    n === 0     (n 怎么来的参考 45行注释)
     *              函数开始执行: console.log(o)    我们传递的是数字 0
     *  
     
     *  a.fun(3)
     *      调用的是 对象 obj 内部的 fun(QF999), 并传递一个 实参 3, 所以对应的一个形参 m === 3
     *          开始执行函数内部代码
     *              return fun(m, n)    传递的两个实参 m === 3;     n === 0     (n 怎么来的参考 45行注释)
     *              函数开始执行: console.log(o)    我们传递的是数字 0
    */