简体   繁体   English

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

[英]How does this JavaScript closure work?

Here's some JavaScript: 这是一些JavaScript:

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

It works. 有用。

linkElem is a local variable that I create in a loop inside a function. linkElem是我在函数内部循环中创建的局部变量。 I assign some data to it with jQuery's .data() . 我用jQuery的.data()为它分配了一些数据。 If I did not call .click() , linkElem would be reassigned during the loop and then recycled after the function returns. 如果我没有调用linkElem .click()linkElem在循环期间重新分配linkElem ,然后在函数返回后再循环。 However, I have created an anonymous function which references linkElem . 但是,我创建了一个引用linkElem的匿名函数。 So I am no longer sure what is going on. 所以我不再确定发生了什么。

My guess is that all of the anonymous functions and linkElem s created during the loop are given UIDs of some kind and moved to persistent/global scope. 我的猜测是在循环期间创建的所有匿名函数和linkElem都被赋予某种UID并移动到持久/全局范围。 Is this correct? 它是否正确? Gratuitous detail would be much appreciated. 无偿的细节将非常感激。

Yes, your description is pretty close. 是的,你的描述非常接近。 The local storage for a Javascript function call is just a block of memory allocated for local variables. Javascript函数调用的本地存储只是为局部变量分配的内存块。 If you "capture" that by creating another function inside a called function, then the storage is retained and the local variables carry on with their lives, unaware that the function that gave them birth might be long dead. 如果你通过被调用函数中创建另一个函数来“捕获”它,那么存储将被保留,并且局部变量会继续存在,并且不知道给它们生出的函数可能已经很久了。

It's important to keep in mind that only functions create such storage — things like brace-enclosed loop bodies are not separate storage areas. 重要的是要记住, 只有函数才能创建这样的存储 - 诸如括号封闭的循环体之类的东西不是单独的存储区域。 Thus a common error is to declare a variable in a function and re-use it among several functions created in a loop. 因此,常见的错误是在函数中声明变量并在循环中创建的多个函数中重用它。 That's not inherently wrong, but the effect can be surprising: 这本身并不是错误的,但效果可能会令人惊讶:

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

If you run that, you'll see three alerts that all say "3". 如果你运行它,你会看到三个警告都说“3”。 Why? 为什么? Because they all share the same "i" variable. 因为它们都共享相同的“i”变量。 You can avoid that by introducing another function layer: 你可以通过引入另一个功能层来避免这种情况

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

The "wrapper" function is just there to provide a local variable (the parameter "private_i") whereto the loop variable "i" can be copied. “包装器”功能就是提供一个局部变量(参数“private_i”),可以复制循环变量“i”。

Please refer to this How do JavaScript closures work? 请参考这个JavaScript闭包如何工作? This may help you understanding closures. 这可以帮助您理解闭包。

Whenever you see the function keyword within another function, the inner function has access to variables in the outer function. 每当在另一个函数中看到function关键字时,内部函数就可以访问外部函数中的变量。

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

This will always alert 16, because bar can access the x which was defined as an argument to foo , and it can also access tmp from foo . 这将始终警告16,因为bar可以访问被定义为foo的参数的x ,并且它还可以从foo访问tmp

That is a closure. 一个关闭。 A function doesn't have to return in order to be called a closure. 函数不必返回以便被称为闭包。 Simply accessing variables outside of your immediate lexical scope creates a closure . 只需访问直接词法范围之外的变量就可以创建一个闭包

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

The above function will also alert 16, because bar can still refer to x and tmp , even though it is no longer directly inside the scope. 上面的函数也会提醒16,因为bar仍然可以引用xtmp ,即使它不再直接在范围内。

However, since tmp is still hanging around inside bar 's closure, it is also being incremented. 然而,由于tmp仍然悬挂在bar的封闭内,它也在增加。 It will be incremented each time you call bar . 每次调用bar时它都会递增。

The simplest example of a closure is this: 最简单的闭包示例是:

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

When a Javascript function is invoked, a new execution context is created. 调用Javascript函数时,将创建新的执行上下文。 Together with the function arguments and the parent object, this execution context also receives all the variables declared outside of it (in the above example, both 'a' and 'b'). 与函数参数和父对象一起,此执行上下文还接收在其外部声明的所有变量(在上面的示例中,“a”和“b”)。

It is possible to create more than one closure function, either by returning a list of them or by setting them to global variables. 可以通过返回它们的列表或将它们设置为全局变量来创建多个闭包函数。 All of these will refer to the same x and the same tmp , they don't make their own copies. 所有这些都将引用相同的 x和相同的tmp ,它们不会制作自己的副本。

[you]: Fascinating, tell me more! [你]:很有意思,告诉我更多!

Here the number x is a literal number. 这里的数字x是一个字面数字。 As with other literals in JavaScript, when foo is called, the number x is copied into foo as its argument x . 与JavaScript中的其他文字一样,当调用foo时,数字x复制foo作为其参数x

On the other hand, JavaScript always uses references when dealing with Objects. 另一方面,JavaScript在处理对象时总是使用引用。 If say, you called foo with an Object, the closure it returns will reference that original Object! 如果说,你用一个Object调用了foo ,它返回的闭包将引用那个原始的Object!

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);

As expected, each call to bar(10) will increment x.memb . 正如预期的那样,每次调用bar(10)都会增加x.memb What might not be expected, is that x is simply referring to the same object as the age variable! 可能没有预料到的是, x只是指与age变量相同的对象! After a couple of calls to bar , age.memb will be 2! 经过几次电话到barage.memb将是2! This referencing is the basis for memory leaks with HTML objects. 此引用是HTML对象的内存泄漏的基础。

However, I have created an anonymous function which references linkElem. 但是,我创建了一个引用linkElem的匿名函数。 So I am no longer sure what is going on. 所以我不再确定发生了什么。

It still gets reassigned, unless you are wrapping it in another level of scope (NB: another function). 它仍然会被重新分配,除非你将它包装在另一个范围内(NB:另一个函数)。

Consider the following: 考虑以下:

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

In this case, all of those links would alert 10 when clicked, because j is outside of the scope and is being updated. 在这种情况下,所有这些链接在单击时都会发出警报10 ,因为j在范围之外并且正在更新。

If you're creating linkElem in the same way, you are likely to only get the result of the last linkElem in the loop. 如果您以相同的方式创建linkElem ,则很可能只获得循环中最后一个 linkElem的结果。

This is a better way: 这是一种更好的方法:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM