• 理解Javascript闭包


    一、闭包的定义

    闭包就是一个函数。什么函数呢?是能访问另一个函数作用域的函数(可以读取其他函数内部变量的函数)。
    由于在Javascript语言中,只有函数内部的子函数才能读取其父函数的局部变量,因此可以把闭包理解为**“定义在一个函数内部的函数”**

    二、闭包的作用

    闭包主要有两个作用:

    1. 可以读取函数内部的变量
    2. 可以让这些变量的值始终保存在内存中

    通过如下代码来理解:

     function f1(){
    
        var n=999;
    
        nAdd=function(){n+=1}
    
        function f2(){
          alert(n);
        }
    
        return f2;
    
      }
    
      var result=f1();
    
      result(); // 999
    
      nAdd();
    
      result(); // 1000
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    上述代码函数f1内部定义了一个函数f2,并将其作为返回值,则f2就是一个闭包。由于将f1的执行结果赋值给变量result,那么result实际上就是闭包f2函数。它一共执行了两次,第一次值是999,第二次值是1000.这证明了f1中局部变量n的值一直保存在内存中,并没有在f1调用后被自动消除。

    为什么会这样呢?因为f2被赋给了一个全局变量,因此f2会始终在内存中,而f2依赖于f1,因此f1也会始终在内存中,不会在调用结束后被垃圾回收机制回收。

    上述代码还有一个需要注意的地方在于nAdd=function(){n+=1},首先在nAdd前面没有关键字,因此nAdd是一个全局变量,而不是局部变量。其次,nAdd的值是一个匿名函数,而这个匿名函数也是一个闭包,因此可以通过nAdd对f1内的局部变量进行操作。

    三、闭包中需要注意的问题

    闭包中需要注意的是,返回的函数并没有立即执行,而是需要再次调用才会执行。
    上述代码中,返回的函数f2并没有立即执行,而是调用了result()才执行的。
    再看一段代码:

    function count() {
        var arr = [];
        for (var i=1; i<=3; i++) {
            arr.push(function () {
                return i * i;
            });
        }
        return arr;
    }
    
    var results = count();
    var f1 = results[0];
    var f2 = results[1];
    var f3 = results[2];
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    上述代码,每次循环都创建一个函数,总共创建了3个函数,并将这3个函数放入数组arr中返回了。
    你可能认为调用f1(),f2(),f3()的结果分别是:1,4,9,但实际结果是:

    f1(); // 16
    f2(); // 16
    f3(); // 16
    
    • 1
    • 2
    • 3

    全部都是16!
    我们分析一下上面代码的执行流程:
    首先var results = count();之后,函数count已经被调用了,所以依次执行函数内的各段代码:var arr = [];,for (var i=1; i<=3; i++),这个for循环尤其值得注意。因为此时循环体执行了push方法,将一个个函数function () { return i * i;}添加到数组内,但是这个函数并没有被调用,还只是一个变量,所以for循环依次执行,直到i = 4。因为闭包,内部函数function () { return i * i;}引用的i就是外部变量,for循环中的i = 4。所以,之后数组arr内的函数的i都是4。

    ​ 调用函数count后,变量results已经是数组arr了。数组里面元素依次是function f1() { return i * i;} function f2() { return i * i;} function f3() { return i * i;}。但是三个函数都没有被调用,直到var f1 = results[0];,此时function f1() { return i * i;}开始执行,如上段所写,此时的i = 4,所以,返回值就是16了。后面两个调用也是类似情况。

    因此返回闭包时牢记的一点就是:返回函数不要引用任何循环变量,或者后续会发生变化的变量。

    如果一定要引用循环变量怎么办?
    **解决方法一:**再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变:

    function count() {
        var arr = [];
        for (var i=1; i<=3; i++) {
            arr.push((function (n) {    //立即执行函数
                return function () {
                    return n * n;
                }
            })(i));
        }
        return arr;
    }
    
    var results = count();
    var f1 = results[0];
    var f2 = results[1];
    var f3 = results[2];
    
    f1(); // 1
    f2(); // 4
    f3(); // 9
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    上述代码创建了一个立即执行函数,将循环变量当前的值绑定给了立即执行函数,由于立即执行函数的存在数组里的值依次是function f1() { return 1 * 1;} function f2() { return 2 * 2;} function f3() { return 3 * 3;}。从而达到了调用f1,f2,f3分别得到1,4,9的效果。

    解决方法2:
    将循环体中的var改成let

    主要参考:
    https://www.liaoxuefeng.com/wiki/1022910821149312/1023021250770016
    https://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures.html

  • 相关阅读:
    NPM v0 包捐赠给 Vercel 后续—— v0.dev
    elasticsearch
    javascript的讲解
    GitBook生成电子书并发布到GitHub Pages
    【小程序原生】
    Leetcode刷题解析——904. 水果成篮
    安装SQL Server详细教程
    pm2 命令手册
    怎么找到没有广告的安全浏览器?这款干净实用
    [附源码]java毕业设计石林县石漠化信息查询分析系统
  • 原文地址:https://blog.csdn.net/m0_46175303/article/details/125601148