Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
380 views
in Technique[技术] by (71.8m points)

function - Closures: Line by Line explanation of "Javascript: Good Parts" example?

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.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...