Замыкания в JavaScript

Замыкание - это ситуация, когда функция запоминает переменные из внешней области видимости и продолжает иметь к ним доступ.

function createGreeting(name) {
  return function () {
    console.log('Привет, ' + name + '!');
  };
}

let greetAnna = createGreeting('Анна');

greetAnna();

Результат:

Привет, Анна!

Функция, которая вернулась из createGreeting(), помнит значение переменной name.

Что такое замыкание

Обычно переменные внутри функции доступны только внутри этой функции.

Но если внутри функции создать другую функцию, внутренняя функция получит доступ к переменным внешней функции.

function outer() {
  let message = 'Сообщение из внешней функции';

  function inner() {
    console.log(message);
  }

  inner();
}

outer();

Результат:

Сообщение из внешней функции

Функция inner() использует переменную message, хотя переменная объявлена не внутри нее, а во внешней функции.

Первый пример

Замыкание становится особенно заметным, когда внешняя функция уже завершилась, а внутренняя функция продолжает работать.

function createMessage() {
  let message = 'JavaScript';

  return function () {
    console.log(message);
  };
}

let showMessage = createMessage();

showMessage();

Результат:

JavaScript

Функция createMessage() уже выполнилась. Но функция showMessage все равно помнит переменную message.

Это и есть замыкание.

Почему переменная не исчезает

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

function createUserName() {
  let userName = 'Игорь';

  return function () {
    return userName;
  };
}

let getUserName = createUserName();

console.log(getUserName());

Результат:

Игорь

Переменная userName не становится глобальной. Она просто остается доступной для функции, которая ее замкнула.

Счетчик на замыкании

Частый пример замыкания - счетчик.

function createCounter() {
  let count = 0;

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

let counter = createCounter();

console.log(counter());
console.log(counter());
console.log(counter());

Результат:

1
2
3

Переменная count хранится внутри createCounter(), но функция counter продолжает иметь к ней доступ.

Снаружи напрямую изменить count нельзя. Можно только вызвать функцию.

Независимые замыкания

Каждый вызов внешней функции создает свое отдельное замыкание.

function createCounter() {
  let count = 0;

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

let firstCounter = createCounter();
let secondCounter = createCounter();

console.log(firstCounter());
console.log(firstCounter());
console.log(secondCounter());

Результат:

1
2
1

firstCounter и secondCounter не делят одну переменную count. У каждого счетчика свой count.

Функции с настройками

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

function createMultiplier(multiplier) {
  return function (number) {
    return number * multiplier;
  };
}

let double = createMultiplier(2);
let triple = createMultiplier(3);

console.log(double(10));
console.log(triple(10));

Результат:

20
30

Функция double помнит значение multiplier равное 2.

Функция triple помнит значение multiplier равное 3.

Где встречаются замыкания

Замыкания встречаются чаще, чем может показаться сначала.

Они появляются, когда функция использует переменные из внешней области. Например, в функциях, которые возвращают другие функции, в callback-функциях и обработчиках событий.

function showLater(message) {
  setTimeout(function () {
    console.log(message);
  }, 1000);
}

showLater('Прошла секунда');

Функция внутри setTimeout() использует переменную message из внешней функции showLater(). Это тоже замыкание.

Итого

1. Замыкание появляется, когда функция использует переменные из внешней области видимости.

2. Внутренняя функция может помнить внешние переменные даже после завершения внешней функции.

3. Замыкания позволяют хранить данные без глобальных переменных.

4. Каждый вызов внешней функции создает свое отдельное замыкание.

5. Замыкания часто встречаются в callback-функциях, обработчиках событий и функциях, которые возвращают другие функции.