【问题标题】:In a closure, why does the main variable not get reset every time it is called?在闭包中,为什么每次调用主变量都不会重置?
【发布时间】:2018-02-12 02:01:19
【问题描述】:

我正在努力深入了解闭包,但我正在努力研究它的机制。我看过 w3schools (https://www.w3schools.com/js/js_function_closures.asp) 和 MDN (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures) 以及其他一些地方。我了解闭包是什么以及如何使其工作,但我不明白为什么在第一次调用外部函数之后的后续调用似乎直接转到内部函数。

这是我的代码 - 一个简单、有效的闭包:

  var add = (function () {
    var a = 0;

    alert('hi');

    function addInner(){
      a += 1;
      return a;
    }
    return addInner;
  })();

  function getAdd() {
    document.getElementById("test").innerHTML = add();
  }

我有两个问题可能都有相同的答案:为什么每次我打电话给add() 时a 都不会被重置?为什么除了第一次没有弹出警报? 我将getAdd() 附加到一个按钮上,效果很好,但它在第一次之后并没有弹出警报。为什么会这样?

编辑:我还发现这个问题的第一个答案 (Why Don't Variables Reset in a Closure (Javascript)) 真的很有帮助。

【问题讨论】:

    标签: javascript closures


    【解决方案1】:

    add 是对addInner 的引用,而不是匿名的“外部”函数,因为“外部”函数返回addInner。然后,您调用该匿名函数一次 - 最后一组 ()- 并将结果函数及其私有“a”存储在 add 中。

    假设外部函数是一个名为constructAdder 的命名函数,而你调用了

    var add = constructAdder();
    var add2 = constructAdder();
    

    这基本上就是你所做的,但如果使用匿名函数,它会更清晰。由于闭包的魔力,你有一个外部函数构造了一个可以计数的内部函数。

    【讨论】:

      【解决方案2】:

      这些问题都有相同的答案。这相当简单。本声明:

      var val = (function() {
        return 42
      }())
      

      将val 设置为 42。不是函数。这称为IIFE,或立即调用的函数表达式。我们正在声明一个函数并立即调用它。我们从不保存该函数。我们只用一次就可以得到它返回的值:

      val // 42
      

      对比:

      var val = function() {
        return 42
      }
      

      在这种情况下,我们将 val 设置为一个函数,调用该函数时返回 42。

      val() // 42
      val() // 42
      

      我们可以对 IIFE 感到非常疯狂:

      var val = (function() {
        return (function() {
          return (function() {
            return 42
          }())
        }())
      }())
      

      那个乱七八糟的返回值还是42。

      val // 42
      

      所有这些函数都被声明一次,使用一次,然后被丢弃。但 IIFE 的功能远不止这些:

      var add = (function() {
        var counter = 0
      
        return function() {
          return counter++
        }
      }())
      

      我们现在正在使用 IIFE 创建一个私有变量! counter 不能在 IIFE 范围之外访问。它是安全的。但是内部函数可以访问和修改它。这是一个完全封装的、有状态的函数!

      add() // 0
      add() // 1
      counter // Uncaught ReferenceError: counter is not defined
      

      这是您的代码正在执行的操作:

      var add = (function () {
        var a = 0;
      
        alert('hi');
      
        function addInner(){
          a += 1;
          return a;
        }
        return addInner;
      })();
      

      它封装了a 变量,这样就不能在IIFE 范围之外访问它。但是,addInner 函数确实可以访问a,并且可以随意修改它。

      alert() 只被调用一次,因为 IIFE 只被调用一次,然后被丢弃。请注意,函数本身是唯一被丢弃的东西。由于addInner 维护对 IIFE 创建的范围(闭包)的引用,因此该范围对垃圾回收是安全的。

      事实上,这是思考 IIFE 的一种有用方式:

      var val = function() {}
      

      创建一个函数并

      val()
      

      创建一个闭包(函数体运行的上下文)。当我们不关心函数时使用 IIFE,我们只需要它创建的闭包。

      希望这会有所帮助。玩得开心!

      【讨论】:

      • 谢谢!我没有意识到外部功能被丢弃了。现在说得通了。我会继续练习这些。
      【解决方案3】:
      var add = (function () {
          var a = 0;
      
          alert('hi');
      
          function addInner(){
            a += 1;
            return a;
          }
          return addInner;
        })(); <-- This
      

      末尾的 () 表示永远添加 add,如果您查看上面的行,那么它会显示 return addInner。这意味着add() 实际上是addInner(),这样做的好处是addInner 可以访问a。其实这样写函数更合适。

      var add = (function () {
          this.a = 0;
      
          alert('hi');
      
          var addInner = function(){
            this.a += 1;
            return this.a;
          }.bind(this);
          return addInner;
        })();
      

      因为现在你会认为add.a 会使a 可用,因为它被写为this.a,但由于addInner 被返回,所以它不可用。

      【讨论】:

        【解决方案4】:

        你应该专注于

        (function(){
            // impl
        })()
        

        块。 它被称为立即调用函数表达式或 IIFE。MDN reference

        因此,当代码执行到第 1 行时,它首先计算右侧,然后 IIFE 返回一个闭包,然后将其分配给变量 add。同时,在 IIFE 中调用了警报。了解 IIFE 可以帮助您解决问题。

        【讨论】:

          【解决方案5】:

          在我看来,关键是IIFE,在第一次执行下面的代码之后,

          var add = (function () {
              var a = 0;
          
              alert('hi');
          
              function addInner(){
                a += 1;
                return a;
              }
              return addInner;
            })();
          

          (注意!上面的代码中没有add(),因为IIFE会立即被调用,return addInner完成了变量add的初始化)

          函数add已更改为:

          add = function addInner() {
              a += 1;
              return a;
          }
          

          当然,alert() 只执行了一次,因为add 一开始就被改变了。

          为什么每次调用 add() 时 a 都没有被重置?

          为什么除了第一次没有弹出提示?

          这就是你问题的答案。

          变量a 没有销毁,因为函数add 仍然有引用,这与closure 有关。

          【讨论】:

            猜你喜欢
            • 2013-06-28
            • 2022-06-27
            • 2017-09-27
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2014-08-19
            • 2018-06-13
            • 2017-05-01
            相关资源
            最近更新 更多