Курс →React JS

для начинающих
от платформы StackDev.ru

Замыкания в Javascript (что такое замыкание в JS + 3 примера)

Дата:
Javascript

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

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

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

Замыкания JS (пример №1)

Для примера, давайте создадим 2 функции:

1// Внешняя функция
2function external() {
3 const externalVar = 'Я - внешняя функция.';
4
5 // Внутренняя функция
6 function internal() {
7 const internalVar = 'Я - внутренняя функция.';
8 console.log(internalVar);
9 console.log(externalVar);
10 }
11
12 // Запускаем функцию internal внутри функции external
13 internal();
14}

Теперь давайте запустим функцию external:

1external();
2// Получаем в лог значения 2-х переменных:
3// Я - внутренняя функция.
4// Я - внешняя функция.

Таким образом, автоматически запускается наша внутренняя функция internal, и мы получаем в консоле 2 лога, которые содержат значения переменных externalVar и internalVal:

  • internalVar, потому что она находится внутри области видимости текущей функции internal.
  • externalVar, потому что она находится внутри области видимости родительской функции - external.

Так где же здесь замыкание?

Мы получим замыкание, если будем запускать нашу функцию internal не внутри родительской функции external, а за ее пределами.

Как это сделать?

Для этого нам нужно сделать 2 вещи:

  1. Мы должны возвратить функцию internal из родительской функции external:
1function external() {
2 const externalVar = 'Я - внешняя функция.';
3
4 // Возвращаем внутреннюю функцию
5 return function internal() {
6 const internalVar = 'Я - внутренняя функция.';
7 console.log(internalVar);
8 console.log(externalVar);
9 };
10}

Если мы сейчас запустим нашу функцию external, то мы получим "определение" внутренней функции "internal".

1external();
2
3// Получаем определение функции internal:
4// ƒ internal() {
5// const internalVar = 'Я - внутренняя функция.';
6// console.log(internalVar);
7// console.log(externalVar);
8// }
  1. Далее, нужно присвоить результат работы функции external какой-либо новой переменной:
1const internalFn = external();

Если мы выведем в лог значение нашей новой переменной internalFn, то мы также получим определение нашей внутренней функции internal.

1console.log(internalFn);
2
3// Получаем определение функции internal:
4// ƒ internal() {
5// const internalVar = 'Я - внутренняя функция.';
6// console.log(internalVar);
7// console.log(externalVar);
8// }
  1. Теперь мы можем запустить нашу внутреннюю функцию internal - за пределами внешней функции external. Для этого используем новую перменную internalFn:
1internalFn();
2
3// Получаем в лог значения 2-х переменных:
4// Я - внутренняя функция.
5// Я - внешняя функция.

Так где же здесь замыкание?

Идея замыкания в JS - заключается в том, что даже после того, как наша внешняя функция завершила свое выполнение, компилятор Javascript оставляет в памяти значение нашей переменной externalVar.
Это значение остается доступным внутри нашей новой функции internalFn - за пределами внешней функции external.

То есть замыкание в JS можно представить себе, как некое "магическое" пространство между внешней и внутренней функциями, которое остается доступным даже после завершения выполнения внешней функции external.

1function external() {
2 // Магическое пространство начинается:
3 const externalVar = 'Я - внешняя функция.';
4 // Магическое пространство завершается
5 return function internal() {
6 const internalVar = 'Я - внутренняя функция.';
7 console.log(internalVar);
8 console.log(externalVar);
9 };
10}

Замыкания Javascript (пример №2)

Для второго примера давайте создадим следующую функцию:

1function createAddress(type) {
2 const address = type.toUpperCase();
3 return function (name) {
4 return `${address} ${name}`;
5 };
6}

Наша функция будет принимать на вход переменную type - тип обращения ("Гражданин" или "Гражданка") и записывать значение в переменную address в верхнем регистре.

Наша внутренняя безымянная функция принимает переменную name - имя человека.

Теперь давайте создадим 2 новые переменные, в которых используем функцию createAddress с 2-мя типами обращений:

1const addressGrazhdanin = createAddress('Гражданин');
2const addressGrazhdanka = createAddress('Гражданка');

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

При этом, в каждом случае - компилятор Javascript "запомнит" разные значения переменной address. Эти значения будут доступны внутри безымянной функции - за пределами внешней функции createAddress (даже после завершения ее выполнения).

То есть, если мы, позже, используем переменную addressGrazhdanin - то получим значение переменной address, равное 'ГРАЖДАНИН'.

1console.log(addressGrazhdanin('Василий'));
2// ГРАЖДАНИН Василий

В случае с переменной addressGrazhdanka - получаем значение переменной address, равное 'ГРАЖДАНКА'.

1console.log(addressGrazhdanka('Александра'));
2// ГРАЖДАНКА Александра

Замыкание JS (пример №3)

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

1function createPlayer(name) {
2 let score = 0;
3 return function scoreCount() {
4 score++;
5 return `${name} - ${score} балла`;
6 };
7}

Ниже, давайте создадим переменные для каждого игрока.

1const playerOne = createPlayer('Василий');
2const playerTwo = createPlayer('Андрей');

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

1playerOne();
2playerOne();
3// Получаем увеличение очков для каждого конкретного игрока
4// "Василий - 2 балла"
1playerTwo();
2playerTwo();
3playerTwo();
4// Получаем увеличение очков для каждого конкретного игрока
5// "Андрей - 3 балла"

Это происходит, потому что при создании переменных playerOne и playerTwo мы создали замыкание со своим собственным значением переменной score.