Проблемы с закрытием Javascript

Итак, я все еще читаю Apress Pro Javascript Techniques, и у меня проблемы с закрытием.

Как утверждает Джон Резиг:

Замыкания позволяют ссылаться на переменные, существующие в родительской функции. Однако он не предоставляет значение переменной во время ее создания; Он предоставляет последнее значение переменной в родительской функции. Наиболее распространенная проблема, при которой вы увидите это событие во время цикла for. Существует одна переменная, используемая в качестве интерферента (например, i). Внутри цикла for создаются новые функции, которые используют замыкание для повторной ссылки на итератор. Проблема заключается в том, что во время вызова новых закрытых функций они будут ссылаться на последнее значение итератора (т. е. на последнюю позицию в массиве), а не на то значение, которое вы ожидаете.

Затем он представляет в листинге 2-16 пример использования анонимных функций для определения области видимости.

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

Этот пример работает так, как ожидалось, и поведение основного объекта правильное.

Далее он в другой раз использует самовыполняющуюся функцию для создания области действия во время итерации.

Цель функции — создать объект, определив геттеры и сеттеры для всех его свойств. В этом случае пример не работает.

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

Вместо этого после передачи параметра i в качестве аргумента самовыполняющейся функции он работает.

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

Мой вопрос:

  • Почему в первом случае (для цикла) параметр i передавать не нужно, а
    во втором (для in) он нужен для корректной работы?

person user278064    schedule 15.08.2011    source источник


Ответы (3)


В первом примере вы объявляете копию содержимого элемента массива в переменной local:

var item = items[i];

Как говорится во встроенном комментарии, здесь мы запоминаем значение в области закрытия.

Во втором примере вместо передачи i в качестве параметра вы также могли бы сделать:

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

Что делает оба примера более похожими.

Точно так же вы можете изменить первый пример, чтобы передать i в качестве параметра, а не объявлять его подробно в переменной.

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

Произвольно зависит от того, объявляете ли вы локальную копию переменной с помощью оператора var или передаете ее в качестве параметра в свою самовыполняющуюся функцию.

Редактировать:

@Zecc получил хороший отзыв в комментариях, который я хотел бы объяснить:

(function (i) {

    // using `i` works as expected. 

}(i));

Тогда как:

(function () {

    var i = i;

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

}());

Это потому, что оператор var variable = value эффективно:

(function () {

    var i;

    i = i;

}());    

а ключевое слово var всегда присваивает следующей за ним переменной (переменным) значение undefined.

person Matt    schedule 15.08.2011
comment
Ух ты. Это только это. Таким образом, я могу избежать передачи параметров, привязывающих параметр к локальной переменной. - person user278064; 15.08.2011
comment
@пользователь: Да. Лично я предпочитаю передавать значение в качестве параметра, а не объявлять его как переменную, поскольку это та же сигнатура функции, которая использовалась бы, если бы вы объявили самовыполняющуюся функцию как именованную и вызвали ее. - person Matt; 15.08.2011
comment
(function(i){})(i) можно рассматривать почти так же, как писать (function(){ var i = i_in_outside_scope; })(). - person Zecc; 15.08.2011
comment
@Matt: В последнем случае переменная i поднимается, верно? Именно по этой причине его значение не определено. - person user278064; 15.08.2011

Это потому, что вы ссылаетесь на i внутри that.get и that.set во втором случае, а в первом случае вы ссылаетесь на item, что является инвариантным.

person Steve Wang    schedule 15.08.2011

В первом примере самовыполняющаяся функция имеет доступ к текущему значению, на которое указывает ссылка i (поскольку она выполняется сразу), она делает копию текущего item с item=item[i], чтобы внутренняя функция для обработчика события , который будет вызван позже, будет ссылаться на правильный элемент.

Если бы вы этого не сделали, внутренняя функция, обработчик событий, поскольку он не выполняется сразу, ссылался бы на i в области действия верхних функций; поскольку for будет долго выполняться, когда вы щелкнете по объекту, скорее всего, он будет ссылаться на последнее значение i. Сохраняя текущий item с item=item[i] в самовыполняющейся функции, вы получаете item правильный текущий item для каждого из них, и поэтому обработчик событий может иметь доступ к правильному значению, последнему значению, помещенному в каждую из локальных item переменных. .

person Eliu    schedule 15.08.2011