Замыкания в Javascript (что такое замыкание в JS + 3 примера)
В этой статье мы с вами разберемся как что такое, и как работают замыкания (closure) в Javascript. Эта тема однозначно будет всплывать в большинстве ваших интервью. Поэтому, нужно обязательно в ней разбираться и быть готовым объяснить ее суть.
На самом деле, в замыканиях нет ничего сложного. Суть замыкания в JS, заключается в возможности одной функции (назовем ее 1-я функция), которая возвращается из родительской функции (2-я функция), получать доступ к переменным, которые находятся в области видимости родительской, то есть 1-й функции.
Тонкий момент заключается в том, что это происходит даже после того, как родительская, то есть 1-я функция завершила свое выполнение.
Замыкания JS (пример №1)
Для примера, давайте создадим 2 функции:
1// Внешняя функция2function external() {3 const externalVar = 'Я - внешняя функция.';45 // Внутренняя функция6 function internal() {7 const internalVar = 'Я - внутренняя функция.';8 console.log(internalVar);9 console.log(externalVar);10 }1112 // Запускаем функцию internal внутри функции external13 internal();14}
Теперь давайте запустим функцию external:
1external();2// Получаем в лог значения 2-х переменных:3// Я - внутренняя функция.4// Я - внешняя функция.
Таким образом, автоматически запускается наша внутренняя функция internal, и мы получаем в консоле 2 лога, которые содержат значения переменных externalVar и internalVal:
- internalVar, потому что она находится внутри области видимости текущей функции internal.
- externalVar, потому что она находится внутри области видимости родительской функции - external.
Так где же здесь замыкание?
Мы получим замыкание, если будем запускать нашу функцию internal не внутри родительской функции external, а за ее пределами.
Как это сделать?
Для этого нам нужно сделать 2 вещи:
- Мы должны возвратить функцию internal из родительской функции external:
1function external() {2 const externalVar = 'Я - внешняя функция.';34 // Возвращаем внутреннюю функцию5 return function internal() {6 const internalVar = 'Я - внутренняя функция.';7 console.log(internalVar);8 console.log(externalVar);9 };10}
Если мы сейчас запустим нашу функцию external, то мы получим "определение" внутренней функции "internal".
1external();23// Получаем определение функции internal:4// ƒ internal() {5// const internalVar = 'Я - внутренняя функция.';6// console.log(internalVar);7// console.log(externalVar);8// }
- Далее, нужно присвоить результат работы функции external какой-либо новой переменной:
1const internalFn = external();
Если мы выведем в лог значение нашей новой переменной internalFn, то мы также получим определение нашей внутренней функции internal.
1console.log(internalFn);23// Получаем определение функции internal:4// ƒ internal() {5// const internalVar = 'Я - внутренняя функция.';6// console.log(internalVar);7// console.log(externalVar);8// }
- Теперь мы можем запустить нашу внутреннюю функцию internal - за пределами внешней функции external. Для этого используем новую перменную internalFn:
1internalFn();23// Получаем в лог значения 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.