我有以下代码段。
function addLinks () {
for (var i=0, link; i<5; i++) {
link = document.createElement("a");
link.innerHTML = "Link " + i;
link.onclick = function () {
alert(i);
};
document.body.appendChild(link);
}
}
以上代码用于生成5个链接,并使用alert事件绑定每个链接以显示当前链接ID。但它不起作用。当您单击生成的链接时,他们都会说“链接5”。
但是以下代码片段可以满足我们的期望。
function addLinks () {
for (var i=0, link; i<5; i++) {
link = document.createElement("a");
link.innerHTML = "Link " + i;
link.onclick = function (num) {
return function () {
alert(num);
};
}(i);
document.body.appendChild(link);
}
}
以上2个片段引自here。正如作者的解释,似乎闭合是神奇的。
但它如何运作以及闭合如何使其发挥作用超出了我的理解范围。为什么第一个不工作而第二个工作?任何人都可以详细解释这个魔法吗?
感谢。
答案 0 :(得分:105)
Quoting myself有关第一个例子的解释:
JavaScript的作用域是函数级的,而不是块级的,创建闭包只意味着将封闭的作用域添加到封闭函数的词法环境中。
循环终止后,函数级变量i的值为5,这就是内部函数“看到”的内容。
在第二个示例中,对于每个迭代步骤,外部函数文字将计算为具有自己的范围和局部变量num
的新函数对象,其值设置为i
的当前值。由于num
永远不会被修改,它将在闭包的生命周期内保持不变:下一个迭代步骤不会覆盖旧值,因为函数对象是独立的。
请记住,这种方法效率很低,因为必须为每个链接创建两个新的函数对象。这是不必要的,因为如果您使用DOM节点进行信息存储,则可以轻松共享它们:
function linkListener() {
alert(this.i);
}
function addLinks () {
for(var i = 0; i < 5; ++i) {
var link = document.createElement('a');
link.appendChild(document.createTextNode('Link ' + i));
link.i = i;
link.onclick = linkListener;
document.body.appendChild(link);
}
}
答案 1 :(得分:78)
我喜欢为厚厚的人写简单的解释,因为我很厚,所以这里......
我们在页面上有5个div,每个div都有一个ID ... div1,div2,div3,div4,div5
jQuery可以做到这一点......
for (var i=1; i<=5; i++) {
$("#div" + i).click ( function() { alert ($(this).index()) } )
}
但真正解决问题(并慢慢建立起来)......
for (var i=1; i<=5; i++) {
$("#div" + i).click (
// TODO: Write function to handle click event
)
}
for (var i=1; i<=5; i++) {
$("#div" + i).click (
function(num) {
// A functions variable values are set WHEN THE FUNCTION IS CALLED!
// PLEASE UNDERSTAND THIS AND YOU ARE HOME AND DRY (took me 2 years)!
// Now the click event is expecting a function as a handler so return it
return function() { alert (num) }
}(i) // We call the function here, passing in i
)
}
如果你无法理解,那么这应该更容易理解并具有相同的效果......
for (var i=1; i<=5; i++) {
function clickHandler(num) {
$("#div" + i).click (
function() { alert (num) }
)
}
clickHandler(i);
}
如果您记得在调用函数时设置了函数变量值(但这使用与之前完全相同的思维过程),这应该很容易理解。
答案 2 :(得分:20)
基本上,在第一个示例中,您将i
处理程序中的onclick
直接绑定到i
处理程序之外的onclick
。因此,当i
处理程序外的onclick
发生更改时,i
处理程序中的onclick
也会发生变化。
在第二个示例中,不是将其绑定到num
处理程序中的onclick
,而是将其传递给函数,然后函数将其绑定到num
中的onclick
。 i
处理程序。当您将其传递给函数时,num
的值被复制,而不是绑定到i
。因此,当num
更改时,{{1}}保持不变。复制的原因是JavaScript中的函数是“闭包”,这意味着一旦将某些内容传递给函数,它就会被“关闭”以进行外部修改。
答案 3 :(得分:17)
其他人已经解释了发生了什么,这是另一种解决方案。
function addLinks () {
for (var i = 0, link; i < 5; i++) {
link = document.createElement("a");
link.innerHTML = "Link " + i;
with ({ n: i }) {
link.onclick = function() {
alert(n);
};
}
document.body.appendChild(link);
}
}
基本上,穷人可以自我约束。
答案 4 :(得分:5)
在第一个示例中,您只需将此函数绑定到onclick事件:
function() {alert(i);};
这意味着在click事件中,js应该警告addlink函数i变量的值。由于for循环(),它的值将为5。
在第二个示例中,您将生成一个与另一个函数绑定的函数:
function (num) {
return function () { alert(num); };
}
这意味着:如果使用值调用,则返回一个警报输入值的函数。例如。致电function(3)
将返回function() { alert(3) };
。
在每次迭代时都使用值i调用此函数,因此您可以为每个链接创建单独的onclick函数。
关键在于,在第一个示例中,您的函数包含一个变量引用,而在第二个示例中,在外部函数的帮助下,您使用实际值替换了引用。这被称为闭包大致是因为你在函数中“包含”变量的当前值而不是保持对它的引用。