Сегодня разбираем полезное понятие из мира программирования — замыкание. Это нужно тем, кто хочет серьёзно заниматься разработкой и говорить со старшими товарищами на одном языке.
Для начала потребуется два термина: область видимости и стек. Если нужно вспомнить, раскрывайте:
Область видимости определяет, к каким переменным функция, команда или другая переменная может получить доступ, а к каким нет. Проще говоря, что каждая функция «видит» — видит ли она те переменные и объекты, которые созданы за её пределами? Видит ли она то, что вложено в функции, которые запускаются внутри неё?
Обычно так: если переменная объявлена в самой программе, к ней можно получить доступ из вложенной функции. А если объявить переменную внутри функции, то не обратиться к ней извне не получится.
Стек — это как список задач при выполнении программы. Сначала компьютер исполняет один код, внутри которого появляется какая-то функция — это как будто отдельная программа. Компьютер откладывает текущую программу, делает себе в стеке пометку «вернуться сюда, когда доделаю новую программу» и исполняет эту новую функцию. Исполнил — смотрит в стек, куда вернуться. Возвращается туда.
Про стек нужно думать, потому что он не безразмерный. Нельзя бесконечно вкладывать функции друг в друга.
Что такое замыкание
Замыкание в программировании — это когда одна функция возвращает как результат своей работы не переменную, а другую функцию. При этом хитрость в том, что внутренняя функция имеет доступ к переменным из внешней функции и может с ними работать в любой момент.
Чаще всего это используют, чтобы сделать переменную, которая на самом деле работает как функция.
Звучит сложно, объясним на примерах.
Пример замыкания в коде
Сделаем функцию, которая вернёт нам фразу «Привет, …» вместе с именем, которое мы в него отправим. Но сделаем это через замыкание — чтобы посмотреть, как именно всё устроено.
Обратите внимание на две переменные в конце: mike и maxim. По сути, эти переменные — ссылки на вызов результата функции hello(), но с конкретным параметром.
// внешняя функция
function hello(name) {
// внутренняя функция-замыкание
// возвращаем её как результат работы внешней
return function() {
// внутренняя функция выводит сообщение на экран
console.log("Привет, " + name);
}
}
// создаём новые переменные, используя замыкание
mike = hello("Миша");
maxim = hello("Максим")
// запускаем внутреннюю функцию с нашими параметрами, просто указав имена переменной
mike();
maxim();
Ещё один пример, посложнее
Только что мы сделали просто — привязали сообщение к переменной без возможности на него повлиять. Но можно создавать такие переменные, у которых могут быть свои параметры — и в них уже передавать любые значения.
Для примера сделаем замыкание и заведём две переменные — каждая будет выдавать сообщение со своим началом фразы, а продолжение будем передавать им в виде параметра:
// внешняя функция, у которой есть свой параметры
function greeting(hi) {
// внутренняя функция-замыкание, тоже со своим параметром
// возвращаем её как результат работы внешней
return function(name) {
// внутренняя функция выводит сообщение на экран
console.log(hi + ", " + name);
}
}
// создаём новые переменные, используя замыкание
morning = greeting("Доброе утро");
thnx = greeting("Спасибо за комментарий")
// запускаем внутреннюю функцию с нашими параметрами, указав параметры вызова
morning("коллеги");
thnx("Павел");
Причём здесь область видимости
Из последнего примера видно, что объявление переменных происходит так:
- переменной присваивается результат выполнения внешней функции;
- этот результат зависит от параметра, который мы ей передали;
- потом, при обращении к переменной, происходит обработка только второго параметра, а первый хранится где-то в памяти и подставляется в нужный момент.
Но как такое может быть, когда внутренняя функция использует переменную из области видимости внешней функции, которой уже нет?
Всё дело в том, что при замыкании создаётся как бы виртуальное окружение, в котором и хранится значение из внешней функции. А вот как это работает с точки зрения области видимости:
Зачем нужны замыкания
На замыканиях строится около половины алгоритмов в функциональном программировании. А ещё на них можно построить много разного:
- изолировать логику выполнения фрагментов кода, если это не позволяют сделать встроенные возможности языка (как в JavaScript);
- лучше структурировать код, особенно при организации функций, которые отличаются только несколькими элементами;
- реализовать инкапсуляцию в тех языках, где её нет.
Что дальше
Следующий шаг — попробовать замыкания в деле. Напишем небольшой код и проверим, как работают замыкания и для чего они могут пригодиться.