Возврат функции из функции - как работает замыкание?

284
26 апреля 2017, 12:14

Учу js и, к сожалению, не понял одну вещь

function makeCounter() {
  var currentCount = 1;
  return function() { // (**)
    return currentCount++;
  };
}
var counter = makeCounter(); // (*)
// каждый вызов увеличивает счётчик и возвращает результат
alert( counter() ); // 1
alert( counter() ); // 2
alert( counter() ); // 3
// создать другой счётчик, он будет независим от первого
var counter2 = makeCounter();
alert( counter2() ); // 1

Вопрос 1: почему при первом вызове функции переменная currentCount = 1, ведь возвращаемое функцией currentCount++, следовательно должно быть 2. Так почему

return function()...

пропускается?

Вопрос 2: почему при вызовах функции currentCount не присваивается 1, ведь

var currentCount = 1;
Answer 1

Когда в строке var counter = makeCounter(); выполняется функция makeCounter, она присваивает значение 1 своей локальной переменной currentCount и возвращает, но не выполняет анонимную функцию. Так как анонимная функция использует переменную из внешней области видимости, создается "замыкание" (closure), которое соединяет экземпляр переменной currentCount с возвращаемым экземпляром анонимной функции.

Answer 2

Попробую объяснить на пальцах.

Данная строка кода

var counter = makeCounter(); // (*)

Делает примерно следующее

  var currentCount = 1;
  var counter = function() { // (**)
    return currentCount++;
  };

Только переменная currentCount не глобальная, а находится в "замыкании", то есть видна исключительно внутри функции. Собственно makeCounter() больше уже не выполняется никогда при вызове counter(), тут сама переменная counter является функцией, которая имеет исключительный доступ к переменной currentCount.

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

Механим создания замыкания примерно такой. Когда вызывается функция для всех ее локальных переменных выделяется область памяти. Когда функция заканчивает свою работу, то эта область памяти очищается - в этом смысл локальных переменных, они живут, пока исполняется функция. Однако если внутри функции (у вас это makeCounter()) объявить другую функцию (которая тоже будет как бы локальной переменной-функцией), а потом вернуть объявленную функцию (она у вас анонимная, но назовем ее counter(), по имени переменной в которой она сохранится) вызывающему коду, то область памяти с локальными переменными будет "жить" до тех пор, пока возвращенная функция "живет" в переменной во внешнем коде. При этом к этой области памяти доступ будет иметь только, возвращенная функция. Возвращенная функция и связанная с ней область памяти называется замыканием. Если вызвать makeCounter() повторно, то выделиться новая область памяти, объявится и вернется новая функция, образуется новое замыкание - поэтому counter'ы независимы друг от друга, так как "смотрят" в разные области памяти и "видят" там разные переменные currentCount'ы

Что касается постфикного (currentCount++) и префиксного (++currentCount) инкремента, то @PeterOslon хорошо объяснил.

Answer 3

Есть разница между префиксный инкремент ++x и постфиксный инкремент x++.

x++ - это возвращает значение x, потом добавляет 1 к x.

++x - добавляет 1 к x, потом возвращает значение x.

Так что

return function() {
  return currentCount++;
};

вырабатывается примерно так:

return function() {
  var temp = currentCount;
  currentCount = currentCount + 1;
  return temp;
};
READ ALSO
Странная задержка в несколько секунд

Странная задержка в несколько секунд

Использую <input id="upload"> для загрузки изображенияПри нажатии на input открывается стандартное окно браузера выбора файла

200
Непонятное поведение api.openweathermap.org

Непонятное поведение api.openweathermap.org

Всем приветНаписал простенький код для телеграм бота:

255
Как устранить Google API 403 Forbidden?

Как устранить Google API 403 Forbidden?

Похоже что мое клиентское приложение действует только для одного аккаунтаКак сделать возможным использовать его для всех?

225