闭包:“Javascript:好部分”的逐行解释例子?

发布于 2024-09-03 07:52:33 字数 1315 浏览 5 评论 0原文

我正在阅读“Javascript:好的部分”,并且对这里到底发生的事情感到完全困惑。更详细和/或简化的解释将不胜感激。

// 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 值,而不是绑定到 add_the_handlers 中定义的 i。返回的函数被分配给 onclick。

I'm reading "Javascript: The Good Parts" and am totally baffled by what's really going on here. A more detailed and/or simplified explanation would be greatly appreciated.

// 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

The add_the_handlers function was intended to give each handler a unique number (i). It fails because the handler functions are bound to the variable i, not the value of the variable i at the time the function was made:

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

Now, instead of assigning a function to onclick, we define a function and immediately invoke it, passing in i. That function will return an event handler function that is bound to the value of i that was passed in, not to the i defined in add_the_handlers. That returned function is assigned to onclick.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

时光磨忆 2024-09-10 07:52:33

我认为对于 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);
}

I think this is a very common source of confusion for newcomers to JavaScript. First I would suggest checking out the following Mozilla Dev article for brief introduction on the topic of closures and lexical scoping:

Let's start with the bad one:

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.
};

We can tackle this problem with more closures, as Crockford suggested in the "good example". A closure is a special kind of object that combines two things: a function, and the environment in which that function was created. In JavaScript, the environment of the closure consists of any local variables that were in-scope at the time that the closure was created:

 // 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.

Rather than having the callbacks all sharing a single environment, the closure function creates a new environment for each one. We could also have used a function factory to create a closure, as in the following example:

function makeOnClickCallback (x) {
   return function (e) {
      alert(x);
   };
}

for (i = 0; i < nodes.length; i += 1) {
   nodes[i].onclick = makeOnClickCallback(i);
}
断舍离 2024-09-10 07:52:33

这都是关于关闭的。在第一个示例中,对于每个单击事件处理程序,“i”将等于“nodes.length”,因为它使用创建事件处理程序的循环中的“i”。当事件处理程序被调用时,循环将结束,因此“i”将等于“nodes.length”。

在第二个示例中,“i”是一个参数(因此是一个局部变量)。事件处理程序将使用局部变量“i”(参数)的值。

It's all about closures. In the first example, "i" will be equal to "nodes.length" for every click event handler, because it uses "i" from the loop which creates the event handlers. By the time the event handler is called, the loop will have ended, so "i" will be equal to "nodes.length".

In the second example, "i" is a parameter (so a local variable). The event handlers will use the value of the local variable "i" (the parameter).

妥活 2024-09-10 07:52:33

在这两个示例中,传递的任何节点都绑定了一个 onclick 事件处理程序(就像 一样,这是不好的做法毕竟)。

不同之处在于,在坏示例中,每个闭包(即事件处理函数)都引用完全相同的 i 变量,因为它们具有共同的父作用域。

这个很好的例子使用了一个立即执行的匿名函数。此匿名函数引用与坏示例中完全相同的 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);
    }
};

这里我们用 newvar 替换了返回的事件处理函数中的 i 并且它仍然有效,因为 newvar > 正是您所期望的 - 从匿名函数的作用域继承的新变量。

祝你好运。

In both examples any node that's passed has an onclick event handler bound to it (just like <img src="..." onclick="myhandler()"/>, which is bad practice after all).

The difference is that in the bad example every closure (the event handler functions, that is) is referencing the exact same i variable due to their common parent scope.

The good example makes use of an anonymous function that gets executed right away. This anonymous function references the exact same i variable as in the bad example BUT since it is executed and provided with i as its first parameter, i's value is assigned to a local variable called ... eh? ... i, exactely - thus overwriting the one defined in the parent's scope.

Let's rewrite the good example to make it all clear:

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

Here we replaced i in the returned event handler function with newvar and it still works, because newvar is just what you'd expect - a new variable inherited from the anonymous function's scope.

Good luck figuring it out.

岁月苍老的讽刺 2024-09-10 07:52:33

这与封闭有关。

当你做坏例子中的事情时,

当你点击每个节点时,你将得到最新的 i 值(即你有 3 个节点,无论你点击哪个节点你都会得到 2)。因为您的警报(i)绑定到变量 i 的引用,而不是绑定到事件处理程序中时 i 的值。

以更好的示例方式进行操作,您将其绑定到迭代时的 i 作为,因此单击节点 1 将为您提供 0,节点 2 将为您提供 1,节点 3 将为您提供 2。

基本上,您当在 }(i) 行调用 i 时,立即评估 i 的值,并将其传递给参数 e,该参数现在保存当时 i 的值。

顺便说一句...我认为更好的示例部分中有一个拼写错误...它应该是alert(e)而不是alert(i)。

It has to do with closure.

When you do the thing in the bad example,

when you click each node, you will get the latest i value (i.e. you have 3 nodes, no matter what node you click you will get 2). since your alert(i) is bound to a reference of variable i and not the value of i at the moment it was bound in the event handler.

Doing it the better example way, you bound it to what i as at the moment that it was iterated on, so clicking on node 1 will give you 0, node 2 will give you 1 and node 3 will give you 2.

basically, you are evaluating what i is immediately when it is called at the line }(i) and it got passed to parameter e which now hold the value of what i is at that moment in time.

Btw... I think there is a typo there in the better example part... it should be alert(e) instead of alert(i).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文