我正在阅读“Javascript:The Good Parts”,我对这里发生的事情感到非常困惑。更加详细和/或简化的解释将非常感激。
// BAD EXAMPLE
// Make a function that assigns event handler functions to an array of nodes the wrong way.
// When you click on a node, an alert box is supposed to display the ordinal of the node.
// But it always displays the number of nodes instead.
var add_the_handlers = function (nodes) {
var i;
for (i = 0; i < nodes.length; i += 1) {
nodes[i].onclick = function (e) {
alert(i);
}
}
};
// END BAD EXAMPLE
add_the_handlers
函数旨在为每个处理程序提供唯一的编号(i)。它失败是因为处理函数绑定到变量i
,而不是函数生成时变量i
的值:
// BETTER EXAMPLE
// Make a function that assigns event handler functions to an array of nodes the right way.
// When you click on a node, an alert box will display the ordinal of the node.
var add_the_handlers = function (nodes) {
var i;
for (i = 0; i < nodes.length; i += 1) {
nodes[i].onclick = function (i) {
return function (e) {
alert(i);
};
}(i);
}
};
现在,我们不是为onclick指定一个函数,而是定义一个函数并立即调用它,传入i
。该函数将返回一个事件处理函数,该函数绑定到传入的i
值,而不是i
中定义的add_the_handlers
。返回的函数被分配给onclick。
答案 0 :(得分:20)
我认为这是JavaScript新手的一个非常常见的混淆源。首先,我建议查看以下Mozilla Dev文章,简要介绍闭包和词法范围的主题:
让我们从糟糕的一开始:
var add_the_handlers = function (nodes) {
// Variable i is declared in the local scope of the add_the_handlers()
// function.
var i;
// Nothing special here. A normal for loop.
for (i = 0; i < nodes.length; i += 1) {
// Now we are going to assign an anonymous function to the onclick property.
nodes[i].onclick = function (e) {
// The problem here is that this anonymous function has become a closure. It
// will be sharing the same local variable environment as the add_the_handlers()
// function. Therefore when the callback is called, the i variable will contain
// the last value it had when add_the_handlers() last returned.
alert(i);
}
}
// The for loop ends, and i === nodes.length. The add_the_handlers() maintains
// the value of i even after it returns. This is why when the callback
// function is invoked, it will always alert the value of nodes.length.
};
我们可以通过更多关闭来解决这个问题,正如Crockford在“好例子”中所说的那样。闭包是一种特殊的对象,它结合了两个东西:一个函数,以及创建该函数的环境。在JavaScript中,闭包的环境包含在创建闭包时在范围内的任何局部变量:
// Now we are creating an anonymous closure that creates its own local
// environment. I renamed the parameter variable x to make it more clear.
nodes[i].onclick = function (x) {
// Variable x will be initialized when this function is called.
// Return the event callback function.
return function (e) {
// We use the local variable from the closure environment, and not the
// one held in the scope of the outer function add_the_handlers().
alert(x);
};
}(i); // We invoke the function immediately to initialize its internal
// environment that will be captured in the closure, and to receive
// the callback function which we need to assign to the onclick.
封闭函数不是让回调都共享一个环境,而是为每个回调函数创建一个新的环境。我们也可以使用函数工厂来创建闭包,如下例所示:
function makeOnClickCallback (x) {
return function (e) {
alert(x);
};
}
for (i = 0; i < nodes.length; i += 1) {
nodes[i].onclick = makeOnClickCallback(i);
}
答案 1 :(得分:3)
一切都与闭包有关。在第一个示例中,对于每个单击事件处理程序,“i”将等于“nodes.length”,因为它使用来自创建事件处理程序的循环中的“i”。当调用事件处理程序时,循环将结束,因此“i”将等于“nodes.length”。
在第二个例子中,“i”是一个参数(所以是局部变量)。事件处理程序将使用局部变量“i”(参数)的值。
答案 2 :(得分:2)
在这两个示例中,任何传递的节点都有一个绑定到它的onclick事件处理程序(就像<img src="..." onclick="myhandler()"/>
一样,毕竟这是不好的做法)。
不同之处在于,在错误的示例中,每个闭包(事件处理函数,即)都会引用完全相同的i
变量,因为它们具有共同的父作用域。
这个好例子使用了一个可以立即执行的匿名函数。此匿名函数引用与错误示例BUT完全相同的i
变量,因为它是执行的,并且i
作为其第一个参数提供,i
的值被赋值给局部变量叫...呃? ...... i
,因此 - 覆盖父母范围内定义的那个。
让我们重写好的例子,让它变得清晰:
var add_the_handlers = function (nodes) {
var i;
for (i = 0; i < nodes.length; i += 1) {
nodes[i].onclick = function (newvar) {
return function (e) {
alert(nevar);
};
}(i);
}
};
在这里,我们用i
替换了返回的事件处理函数中的newvar
,它仍然有效,因为newvar
正是您所期望的 - 一个从匿名函数继承的新变量范围。
祝你好运。
答案 3 :(得分:0)
这与关闭有关。
当你在坏例子中做事时,
当您单击每个节点时,您将获得最新的i值(即,您有3个节点,无论您点击哪个节点,您都将获得2个节点)。因为你的警报(i)被绑定到变量i的引用而不是i在事件处理程序中绑定时的值。
以更好的示例方式做到这一点,你将它绑定到我迭代的那一刻,所以点击节点1将给你0,节点2将给你1,节点3将给你2。
基本上,你正在评估我在线上调用它时的立即}(i)并且它被传递给参数e,现在它保存了那个时刻的值。
顺便说一下......我认为在更好的示例部分中有一个拼写错误...它应该是警报(e)而不是警报(i)。