【问题标题】:How does this JavaScript closure work?这个 JavaScript 闭包是如何工作的?
【发布时间】:2011-03-02 05:20:39
【问题描述】:

这里有一些 JavaScript:

linkElem.click(function () {
    var data = linkElem.data();
    alert(''+data.mls + ' ' + data.id);
});

有效。

linkElem 是我在函数内部的循环中创建的局部变量。我使用 jQuery 的.data() 为其分配了一些数据。如果我没有调用.click()linkElem 将在循环期间重新分配,然后在函数返回后回收。但是,我创建了一个引用linkElem 的匿名函数。所以我不再确定发生了什么。

我的猜测是,在循环期间创建的所有匿名函数和linkElems 都被赋予了某种类型的 UID,并被移至持久/全局范围。它是否正确?非常感谢您提供无偿的详细信息。

【问题讨论】:

    标签: javascript jquery closures


    【解决方案1】:

    是的,您的描述非常接近。 Javascript 函数调用的本地存储只是为局部变量分配的一块内存。如果您通过创建 another 函数 inside 一个被调用的函数来“捕获”它,那么存储空间会被保留,并且局部变量会继续它们的生命,而不知道给出的函数他们的出生可能早就死了。

    重要的是要记住只有函数会创建这样的存储空间——像大括号封闭的循环体这样的东西不是单独的存储区域。因此,一个常见的错误是在函数中声明一个变量并在循环中创建的几个函数中重复使用它。这本身并没有错,但效果可能令人惊讶:

    function whatever() {
      for (var i = 0; i < 3; ++i) {
        setTimeout(function() { alert(i); }, 5000);
      }
    }
    

    如果您运行它,您会看到三个警报,它们都显示为“3”。为什么?因为它们都共享同一个“i”变量。您可以通过引入另一个功能层来避免这种情况:

    function whatever() {
      for (var i = 0; i < 3; ++i) {
        setTimeout((function(private_i) { return function() { alert(private_i); }; })(i), 5000);
      }
    }
    

    “包装器”函数只是提供一个局部变量(参数“private_i”),循环变量“i”可以复制到该变量中。

    【讨论】:

      【解决方案2】:

      但是,我创建了一个引用 linkElem 的匿名函数。所以我不再确定发生了什么。

      它仍然会被重新分配,除非你将它包装在另一个范围的范围内(注意:另一个函数)。

      考虑以下几点:

      for (var j = 0;j < 10;j += 1) {
          arrayOfLinks[j].onclick = function () {
              alert(j);
          };
      }
      

      在这种情况下,所有这些链接都会在单击时提醒10,因为j 超出范围并且正在更新。

      如果您以同样的方式创建linkElem,您很可能只会在循环中获得last linkElem 的结果。

      这是一个更好的方法:

      linkElem.click(function () {
          var data = $(this).data(); // no longer dependent on `linkElem` reference
          alert(''+data.mls + ' ' + data.id);
      });
      

      【讨论】:

        【解决方案3】:

        请参考这个How do JavaScript closures work? 这可以帮助你理解闭包。

        每当您在另一个函数中看到 function 关键字时,内部函数就可以访问外部函数中的变量。

        function foo(x) {
          var tmp = 3;
          function bar(y) {
            alert(x + y + (++tmp));
          }
          bar(10);
        }
        foo(2)
        

        这将始终警告 16,因为bar 可以访问被定义为foo 的参数的x,它还可以从foo 访问tmp

        一个闭包。一个函数不必为了被称为​​闭包而返回简单地访问直接词法范围之外的变量会创建一个闭包

        function foo(x) {
          var tmp = 3;
          return function (y) {
            alert(x + y + (++tmp));
          }
        }
        var bar = foo(2); // bar is now a closure.
        bar(10);
        

        上述函数也会提醒 16,因为 bar 仍然可以引用 xtmp,即使它不再直接在作用域内。

        然而,由于tmp 仍然在bar 的闭包中徘徊,它也在增加。每次调用 bar 时都会递增。

        最简单的闭包示例如下:

        var a = 10;
        function test() {
          console.log(a); // will output 10
          console.log(b); // will output 6
        }
        var b = 6;
        test();
        

        当调用 Javascript 函数时,会创建一个新的执行上下文。连同函数参数和父对象,此执行上下文还接收在其外部声明的所有变量(在上面的示例中,“a”和“b”)。

        可以通过返回它们的列表或将它们设置为全局变量来创建多个闭包函数。所有这些都将引用相同的x和相同的tmp,它们不会自己制作副本。

        [你]:太迷人了,告诉我更多!

        这里的数字x 是一个文字数字。与 JavaScript 中的其他文字一样,当调用 foo 时,数字 x复制foo 作为其参数 x

        另一方面,JavaScript 在处理对象时总是使用引用。如果说,你用一个对象调用foo,它返回的闭包将引用那个原始对象!

        function foo(x) {
          var tmp = 3;
          return function (y) {
            alert(x + y + tmp);
            x.memb = x.memb ? x.memb + 1 : 1;
            alert(x.memb);
          }
        }
        var age = new Number(2);
        var bar = foo(age); // bar is now a closure referencing age.
        bar(10);
        

        正如预期的那样,每次调用bar(10) 都会增加x.memb。可能出乎意料的是,x 只是指与age 变量相同的对象!在给bar 打了几次电话后,age.memb 将是 2!这种引用是 HTML 对象内存泄漏的基础。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-08-18
          • 2013-09-20
          • 1970-01-01
          • 2014-12-04
          相关资源
          最近更新 更多