Проблемы закрытия Javascript - PullRequest
3 голосов
/ 15 августа 2011

Итак, я все еще читаю 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, в то время как
    во втором (для входа) необходимо для правильной работы?

Ответы [ 3 ]

2 голосов
/ 15 августа 2011

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

2 голосов
/ 15 августа 2011

В первом примере вы объявляете копию содержимого элемента массива в переменной 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, или передаете ее в качестве параметра в вашу самореализующуюся функцию.

Edit:

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

1 голос
/ 15 августа 2011

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

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

...