Javascript closures issues

2019-07-07 04:59发布

So, I'm still reading Apress Pro Javascript Techniques and i'm having troubles with closures.

As John Resig states:

Closures allow you to reference variables that exist within the parent function. However it does not provide the value of the variable at the time it is created; It provides the last value of the variable withing the parent function. The most common issue under which you'll see this occurr during a for loop. There is one variable being used as an interaor (e.g., i). Inside of the for loop, new functions are being created that utilize the closure to reference the iterator again. The rpoblem is tat the time the new closured functions are called, they will reference the last value of the iterator (i.e., the last position in an array), not the value taht you woul expect.

Then he presents, in listing 2-16 an example using anonymous functions to induce scope.

/**
 * Listing 2-16. Example of Using Anonymous Functions to induce the 
 * Scope Needed to Create Multiple Closure-Using Functions 
 */
// An element with an ID of main
var obj = document.getElementById("main");

// An array of items to bind to
var items = ["click", "keypress"];

for (var i = 0; i < items.length; i++) {
    // Use a self executed anonymous function to induce scope
    (function() {
        // Remembre the value within this scope
        var item = items[i];

        // Bind a function to the element
        obj["on" + item] = function() {
            // item refers to a parent variable that has been successfully
            // scoped within the context of this loop
            alert("thanks for your " + item);
        };
    })();               
}

This example works as expected, and the behavious of the main object is correct.

The in the following, it uses another time a self-executing function to induce scope, during an iteration.

The purpose of the function is to create an object, defining getters and setters for all its properties. In this case, the example does not work.

/**
 * Listing 2-25. Example of Dynamicaaly Generated Methods That Are Created 
 * When a New Object is instantiated
 */          
// Create a new user object that accepts an object of properties
function User(properties) {
    // Iterate thorugh the properties of the object, and make sure
    // that it's properly scoped (sas discussed previously)
    var that = this;

    for (var i in properties) { 
       (function() {
           console.log("property: " + i);
           // Create a nwe getter for the property
           that["get" + i] = function() {
               return properties[i];
            };

            // Create a new setter  for the property
           that["set" + i] = function(val) {
               properties[i] = val;
           };
       })();                    
    }
}

// Create a new user object instance and pass in an object of
// properties to seed it with
var user = new User({
    name: "Bob",
    age: 44
});

// Just note that the name property does not exists, as it's private within the  
// properties object
alert(user.name == null);

// However, we're able to access its value using the new getnaem()
// method that was dynamically generated
console.log("name: " + user.getname());  // name: 44 :(
alert(user.getname() == "Bob");

// Finally, we can see that it's possible to set and gt the age using
// the newly generated functions

user.setage(22);
alert(user.getage() == 22);

Instead, after passing the i parameter as argument to the self-executing function,it works.

for (var i in properties) { 
       (function(prop) {
           console.log("property: " + i);
           // Create a nwe getter for the property
           that["get" + prop] = function() {
               return properties[prop];
            };

            // Create a new setter  for the property
           that["set" + prop] = function(val) {
               properties[prop] = val;
           };
       })(i);                   
    }

My question is:

  • Why in the first case (for loop), it is not necessary to pass the i parameter, while
    in the second (for in) it is needed in order to work properly?

3条回答
神经病院院长
2楼-- · 2019-07-07 05:09

It's because you're referencing i within that.get and that.set in the second case, while in the first case, you're referencing item, which is invariant.

查看更多
The star\"
3楼-- · 2019-07-07 05:12

In the first example, the self-executing function has access to the current value that the reference i points to (since it is executed right away), it makes a copy of the current item with item=item[i] so that the inner function for the event handler, which will be called later, will reference the correct item.

If you wouldn't do that the inner function, the event handler, since it doesn't execute right away would reference to the i in the top functions' scope; since the for will be long finished executing when you get to click the thing, it would reference to the last value of i most probably. By keeping the current item with item=item[i] in the self executing function you get in item the correct current item for each one of them and so the event-handler can have access to the right value, the last value placed in each of the local item vars.

查看更多
Fickle 薄情
4楼-- · 2019-07-07 05:15

In the first example you are declaring a copy of the contents of the array element in a local variable:

var item = items[i];

As the inline comment says, here we're remembering the value within the closure scope.

In the 2nd example, instead of passing i as a parameter, you could also have done:

   (function() {

       var prop = i; // See here!

       console.log("property: " + i);
       // Create a nwe getter for the property
       that["get" + prop] = function() {
           return properties[prop];
        };

        // Create a new setter  for the property
       that["set" + prop] = function(val) {
           properties[prop] = val;
       };
   })(); 

Which makes both examples more similar.

Similarly, you could also alter the first example to pass i as a parameter, rather than declaring it verbosely in a variable.

(function(item) {
    // Bind a function to the element
    obj["on" + items[item] = function() {
        // item refers to a parent variable that has been successfully
        // scoped within the context of this loop
        alert("thanks for your " + items[item]);
    };
})(i);     

It's arbitrary as to whether you declare a local copy of the variable using a var statement, or pass it as a parameter into your self executing function.

Edit:

@Zecc bought up a good point in the comments, which I'd like to explain:

(function (i) {

    // using `i` works as expected. 

}(i));

Where as:

(function () {

    var i = i;

    // using `i` doesn't work... i = undefined.

}());

This is because the var variable = value statement is effectively:

(function () {

    var i;

    i = i;

}());    

and the var keyword always assigns the variable(s) following it with the value undefined.

查看更多
登录 后发表回答