Javascript замыкания - переменные против параметров - PullRequest
10 голосов
/ 27 марта 2011

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

var links = document.getElementsByTagName('a');

for (var x=0; x<links.length; x++) attachListener();

function attachListener() {
        links[x].addEventListener('click', function(){
            console.log(x);
        }, false);
};

Когда у меня в документе три ссылки, при щелчке по любой ссылке отображается «3», я думаю, потому что x был увеличен до 3 после последнего запуска цикла.В этом превосходном вступлении я прочитал, что если вы запускаете внешнюю функцию несколько раз, каждый раз создается новое замыкание.Так почему же каждое замыкание не сохраняет различное значение для x каждый раз, когда я вызываю внешнюю функцию?

Когда вы передаете x в качестве параметра внешней функции, оно работает как положено.

var links = document.getElementsByTagName('a');

for (x=0; x<links.length; x++) attachListener(x);

function attachListener(z) {
        links[z].addEventListener('click', function(){
            console.log(z);
        }, false);
};

Теперь вы получаете 0, когда нажимаете на первую ссылку, 1 на вторую и т. Д.

Может кто-нибудь объяснить, почему есть такая разница?

Приветствия

Ответы [ 4 ]

11 голосов
/ 27 марта 2011

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

var makeMutablePoint = function(x, y) {
  return {
    position: function() {
      return [x, y];
    },
    add: function(dx, dy) {
      x = x + dx;
      y = y + dy;
    }
  };
};

Это также способ, которым замыкания работают в большинстве других языков (что является основной причиной, по которой Python иногда говорят, что не имеет правильные замыкания).

Однако есть один аспект, специфичный для JavaScript, который иногда может сбить вас с толку (и на самом деле, кажется, в этом случае так и есть): переменные всегда имеют область действия в JavaScript. Например, в вашем первом фрагменте кода есть только одна переменная x, тогда как можно ожидать, что область действия x будет ограничена телом цикла (с новым x для каждой итерации). Это особенность языка, которая, вероятно, будет улучшена в будущем введением ключевого слова let с более детальными правилами области видимости.

3 голосов
/ 27 марта 2011

Меня несколько раздражало то же самое поведение в прошлом.Мне кажется, это ошибка в реализации замыканий.Предполагается, что замыкание включает моментальный снимок «лексической среды функции (например, набора доступных переменных и их значений) в момент создания замыкания » (источник: Википедия; выделено мое).Очевидно, что это не совсем то, что происходит в этом примере.

Но достаточно просто выяснить, что происходит за кулисами.В вашем первом примере есть только один экземпляр переменной x, и при создании замыкания среда выполнения JavaScript сохраняет в нем ссылку на x вместо копии текущего значения x ввремя закрытия.Поэтому, когда цикл увеличивается на x, «копия» в замыкании также увеличивается.

Во втором случае вы передаете x в качестве параметра функции, которая копирует текущее значениеx в новую переменную, когда она передается в функцию attachListener().Значение этой копии никогда не обновляется (т. Е. Оно отделено от x, и вы не изменяете его внутри attachListener()), и, следовательно, замыкание работает, как ожидается, поскольку оно хранит ссылку на копию, а не наоригинал x.

3 голосов
/ 27 марта 2011

В вашем первом примере x является частью области видимости внешней функции, которая присоединяет прослушиватели событий и поэтому увеличивается до 3.

(outer scope)
    x
attachListener
    (no local variables)

Во втором примере z становится частью области действия attachListener, которая сохраняется в замыкании.

(outer scope)
    x
attachListener
    z
0 голосов
/ 27 сентября 2013

Это распространенная ошибка (также известная как проблема печально известного цикла Javascript ), вызванная неправильным пониманием замыканий. Вот очень простой пример:

var funcs = [];
for (var i = 0; i < 5; i++) {
funcs[i] = function() { return i; };
}
> funcs[0]();
5
> funcs[1]();
5

Главное, что здесь нужно понять, это то, что замыкание закрывает (или запоминает, или «захватывает») ссылки на нелокальные (или свободные) переменные. Теперь акцент делается на ссылок , т. Е. Замыкание не захватывает значения свободных переменных, а захватывает ссылки на их имена.

Если замыкание запомнит значения свободных переменных, то у нас будет (ошибочно) ожидаемое поведение funcs[0](), возвращающее 0 и т. Д. Если замыкание запомнит значения свободных переменных, то мы можем сказать, что замыкание делает «снимок» этих значений в данный конкретный момент времени.

Но это не то, что делает закрытие.

Закрытия запоминают ссылки на свободные переменные, а не их значения.

В этом примере funcs[i] запоминает ссылку на i. При вызове он ищет значение глобальной переменной i, и это значение в настоящее время равно 5.

Что действительно помогло мне понять это, так это эта лекция стр. 12-15, из которой я использовал некоторые выдержки вместе с определением Википедии .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...