这个JavaScript闭包是如何工作的?

4
这里有一些 JavaScript 代码:
linkElem.click(function () {
    var data = linkElem.data();
    alert(''+data.mls + ' ' + data.id);
});

它工作了。

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

我的猜测是,在循环期间创建的所有匿名函数和linkElem都被赋予了某种UID,并移到了持久/全局范围。这正确吗?非常感谢冗长的细节。

3个回答

4
是的,你的描述非常接近。对于Javascript函数调用而言,本地存储只是为局部变量分配的一块内存块。如果通过在被调用的函数内创建另一个函数来“捕获”它,则存储将被保留,并且局部变量将继续存在,不知道赋予它们生命的函数可能已经消失了。
需要牢记的是,仅有函数才会创建这样的存储区域——像大括号包含的循环体并不是单独的存储区域。因此,一个常见的错误是在函数中声明变量,并在循环中创建的多个函数之间重复使用它。这本质上并没有错,但效果可能会让人惊讶:
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"可以被复制到其中。

1
然而,我创建了一个引用linkElem的匿名函数。所以我不确定正在发生什么。
除非您将其包装在另一个作用域级别(注:另一个函数)中,否则它仍然会被重新分配。
考虑以下内容:
for (var j = 0;j < 10;j += 1) {
    arrayOfLinks[j].onclick = function () {
        alert(j);
    };
}

在这种情况下,当单击所有这些链接时,它们都会触发警报10,因为j在范围之外并且正在更新。
如果您以同样的方式创建linkElem,则很可能只会得到循环中最后一个linkElem的结果。
这是更好的方法:
linkElem.click(function () {
    var data = $(this).data(); // no longer dependent on `linkElem` reference
    alert(''+data.mls + ' ' + data.id);
});

1
请参考JavaScript闭包是如何工作的?,这可能会帮助您理解闭包。
每当您在另一个函数中看到函数关键字时,内部函数就可以访问外部函数中的变量。
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作为其参数x被复制到foo中。
另一方面,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对象内存泄漏的基础。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接