Javascript-форум (https://javascript.ru/forum/)
-   Общие вопросы Javascript (https://javascript.ru/forum/misc/)
-   -   Вопрос по замыканиям (https://javascript.ru/forum/misc/15644-vopros-po-zamykaniyam.html)

Goodfella 07.03.2011 21:55

Вопрос по замыканиям
 
Добрый вечер друзья, возник вопрос, скорей даже не вопрос, а полная неразбериха по теме замыканий (недавно в JS). Что бы без длинных предысторий, сразу покажу код (взял из Википедии):
function createCounter() {
    var numberOfCalls = 0;
    return function() {
        return ++numberOfCalls;
    }
}
var fn = createCounter();
fn(); //1
fn(); //2
fn(); //3

Я понимаю, что этот код делает, но абсолютно не понимаю каким образом.
Почему при вызове функции createCounter() не обнуляется переменная numberOfCalls?
Зачем нужно копировать ссылку на функцию в переменную fn, почему ее нельзя вызвать напрямую?
Сам по себе, этот код меня не волнует, но это самый простой пример замыкания, который я, к сожалению, не могу понять. Я бы был очень благодарен, если бы вы подробно объяснили мне ситуацию, как это все считывает интерпретатор.

Kolyaj 07.03.2011 21:59

Цитата:

Сообщение от Goodfella
Почему при вызове функции createCounter() не обнуляется переменная numberOfCalls?

Он обнуляется.

Цитата:

Сообщение от Goodfella
Зачем нужно копировать ссылку на функцию в переменную fn, почему ее нельзя вызвать напрямую?

Ссылка на функцию не копируется. Переменной fn присваивается результат работы функции createCounter().

Goodfella 07.03.2011 22:07

Цитата:

Сообщение от Kolyaj
Он обнуляется.

Меня ввел в недоумение метод alert(numberOfCalls), если его поставить перед
return ++numberOfCalls;

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

Kolyaj 07.03.2011 22:40

При вызове функции createCounter обнуляется. Она у вас только один раз вызывается.

Goodfella 07.03.2011 22:53

Цитата:

Сообщение от Kolyaj
При вызове функции createCounter обнуляется. Она у вас только один раз вызывается.

Да, спасибо, я чуть-чуть посидел - разобрался с этим, но все же, если мы записываем в переменную fn именно значение возвращаемое функцией createCounter(), что значит fn()? Я это воспринимаю как "вызвать значение", что есть абсурд.

Aetae 07.03.2011 22:54

Вот так будет работать как вы хотите, только это не имеет смысла))
function createCounter() {
    var numberOfCalls = 0;
    return function() {
        return ++numberOfCalls;
    }
}
var fn = createCounter;
fn()(); //1
fn()(); //1
fn()(); //1

Kolyaj 07.03.2011 22:58

Цитата:

Сообщение от Goodfella
Я это воспринимаю как "вызвать значение", что есть абсурд.

Почему абсурд, если значением является функция? А createCounter возвращает функцию.

Goodfella 07.03.2011 23:13

Цитата:

Сообщение от Kolyaj
Почему абсурд, если значением является функция? А createCounter возвращает функцию.

Ага! То есть, фактически, fn() вызывает функцию
function() {
    return ++numberOfCalls;
}

Спасибо большое, вы очень помогли!
Если бы еще понять вот этот синтаксис, fn()(), который все таки вызывает фукцию createCounter(), но я думаю, это уже не так проблематично.

Aetae 08.03.2011 01:09

var fn = createCounter;

Вот в чем дело, лол.)

А по поводу fn()() - ты это сколько угодно:
function џ() {
    return function() {
        return function() {
            return function() {
                return function() {
                    return function() {
                        return function() {
                            return function() {
                                alert('lol')
                            }
                        }
                    }
                }
            }
        }
    }
}
џ()()()()()()()()

хотя всё это фигнестрадание)

Goodfella 08.03.2011 02:29

Цитата:

Сообщение от Aetae
хотя всё это фигнестрадание)

Это ради понимания синтаксиса, не для решения конкретной задачи, но принцип уловил, спасибо.

Sweet 08.03.2011 02:33

Цитата:

Сообщение от Kolyaj
При вызове функции createCounter обнуляется.

Ничего не обнуляется:
function createCounter(){
  var numberOfCalls = 0;
  return function(){
    return ++numberOfCalls;
  };
};
var fn1 = createCounter();
fn1(), fn1();
var fn2 = createCounter();
fn2();
alert( fn1() ); // Ведь 3, а не 2
Aetae, как-то жестоко.:) Так по-приличнее, по-моему:
function џ(){
  var count = 1;
  return function(){
    if(++count === 7) return 'lol';
    return arguments.callee;
  };
};

alert( џ()()()()()()() );

Goodfella 08.03.2011 12:48

А вот еще, хотелось бы еще больше прояснить ситуацию:
Что происходит с переменной numberOfCalls? То есть, как я понимаю, у нас выходит 3 области видимости [scope1] - глобальная, [scope2] - область функции createCounter, [scope3] - вложенная функция.
После выхода из функции все эти области остаются жить благодаря замыканию, самое интересное вот что:
Переменная numberOfCalls копируется и используется как свойство [scope3], или же мы просто получаем ссылку на [scope2].numberOfCalls(условно), и уже манимулируем собственно ею.
Я подозреваю, что копируется (или же у меня в голове просто полная каша), а вот что было бы с объектом?

Sweet 08.03.2011 13:30

Цитата:

Сообщение от Goodfella
После выхода из функции все эти области остаются жить благодаря замыканию

Благодаря замыканию остаются жить только замкнутые переменные. И ничего никуда не копируется. Как-то проще нужно к этому относится:)

Aetae 08.03.2011 13:39

Sweet,
Ну так фишка то в использовании)):
function џ(b) {
    return function(a) {b=b|a;
        return function(a) {b=b&a;
            return function(a) {b/=a;
                return function(a) {b-=a;
                    return function(a) {b*=a;
                        return function(a) {b+=a;
                            return function(a) {b='Результат:'+a;
                                alert(b)
                            }
                        }
                    }
                }
            }
        }
    }
}
џ(4)(5)(2)(4)(7)(9)(2)(1)

Но опять же это бессмысленно)

Goodfella 08.03.2011 13:48

Цитата:

Сообщение от Sweet (Сообщение 95527)
Благодаря замыканию остаются жить только замкнутые переменные. И ничего никуда не копируется. Как-то проще нужно к этому относится:)

Я судил по рассуждениям Флэнагана (копи-паст):
Цитата:

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

Sweet 08.03.2011 13:49

Цитата:

Сообщение от Aetae
Ну так фишка то в использовании

Да я понимаю;)

UPD: Goodfella, извините, но мой мозг не способен переварить этот поток мыслей - я просто не понимаю, что Флэнаган пытается втереть:) Нельзя по утрам так людей грузить:)

Kolyaj 08.03.2011 18:58

Цитата:

Сообщение от Goodfella
Переменная numberOfCalls копируется и используется как свойство [scope3], или же мы просто получаем ссылку на [scope2].numberOfCalls(условно), и уже манимулируем собственно ею.

Второе.

Goodfella 08.03.2011 22:21

Цитата:

Сообщение от Kolyaj
Второе.

Просто вот почему спрашиваю, взгляните на код:
function createCounter() {
	var numberOfCalls = 0;
    alert(numberOfCalls);
	return function() {
		alert(numberOfCalls);
    return ++numberOfCalls;
   }
}
var fn = createCounter();
fn(); //Выводит сообщение "0", "0", все ясно, первый вызов.
fn(); //Вызывается только вложенная функция, alert выводит "1", опять же окей.
createCounter(); //Вызывается вся функция, переменная "numberOfCalls" обнулена, alert объемлющей функции выводит "0", годиться.
fn(); //Вызывается опять только вложенная, хотя, по идее "numberOfCalls" был обнулен, alert вывел "2", вот это - не понятно.

Kolyaj 08.03.2011 22:22

При втором вызове новый [scope2] создаётся.

Goodfella 08.03.2011 23:50

Цитата:

Сообщение от Kolyaj
При втором вызове новый [scope2] создаётся.

Учел, спасибо. То есть, к старому [scope2], доступ потерян?

Aetae 09.03.2011 01:21

Нет, он замкнут на новосозданную функцию.
function createCounter() {
    var i=99;
    var j='txt'
    return function(a) {
        alert(eval(a||'"none"')) //i и j нигде не упоминаются
   }
}
var fn = createCounter();
fn();
fn('i');
fn('j');


Вызывая же createCounter() второй раз вы ничего не обнуляете - это уже совершенно другая функция с своим scope.


Часовой пояс GMT +3, время: 06:44.