Оглавление
Время чтения: 20 минут
Автор статьи: Александр Медовник технический директор Appfox.
Введение: замыкания в современной разработке
Меня зовут Александр, я CTO компании AppFox. Мы более 10-ти лет занимаемся заказной разработкой и, также, имеем собственные продукты.
В этой статье мы рассмотрим, что такое замыкание и как оно реализовано в разных языках программирования.
Замыкания (closures) — это концепция, которая пронизывает все современные языки программирования. Понимание замыканий критически важно для:
- Создания чистого, модульного кода
- Реализации сложных паттернов проектирования
- Эффективной работы с асинхронными операциями
- Построения реактивных интерфейсов
В своих видеороликах я описал замыкание для самых маленьких на примере Python
Советую посмотреть их, если данная статья покажется сложной.
Суть замыканий: функции с памятью
Замыкание — это функция, которая сохраняет доступ к переменным из своей лексической области видимости даже после завершения работы внешней функции.
Пример банковского счета


В данном примере замыкание создается следующим образом:
- Создание внешней функции:
- createAccount - это фабричная функция, которая инициализирует состояние счета (balance и transactionCount)
- Захват переменных:
Внутренние методы (deposit, withdraw, getBalance, getTransactionCount) образуют замыкание, так как они:- Используют переменные balance и transactionCount из внешней функции
- Продолжают иметь доступ к этим переменным после завершения работы createAccount
- Механизм работы:
При вызове createAccount(500):- Создается лексическое окружение с balance = 500 и transactionCount = 0
- Возвращается объект с методами, которые "запоминают" это окружение
Когда мы вызываем account.deposit(200):- Функция deposit обращается к переменной balance из сохраненного окружения
- Модифицирует ее значение
- Увеличивает счетчик транзакций
- Инкапсуляция состояния:
- Переменные balance и transactionCount полностью защищены от внешнего доступа
- Изменить их можно только через предоставленные методы
- Каждый вызов createAccount создает новое независимое замыкание с собственным состоянием
- Жизненный цикл:
- Лексическое окружение (с balance и transactionCount) продолжает существовать до тех пор, пока существует хотя бы одна ссылка на возвращенный объект с методами
- Когда account перестает быть нужен и сборщик мусора удаляет его, окружение тоже удаляется
Это классический пример использования замыканий для:
- Создания приватного состояния
- Инкапсуляции бизнес-логики
- Реализации объектно-ориентированного подхода без классов
Главная "магия" замыкания здесь в том, что методы объекта продолжают иметь доступ к переменным balance и transactionCount даже после того, как функция createAccount завершила свою работу.
Как работают замыкания: технические детали
Механизм замыканий состоит из трех ключевых компонентов:
- Лексическое окружение — структура данных, хранящая переменные
- Ссылка на внешнее окружение — связь с родительской областью видимости
- Гарантия сохранения — окружение не удаляется, пока существует замыкание
Практические применения замыканий
Инкапсуляция данных
Замыкания позволяют создавать истинно приватные переменные без использования классов.

В этом примере реализован простой таймер с использованием замыкания:
Создание приватного состояния

- Переменная startTime инициализируется текущим временем при создании таймера
- Эта переменная является приватной - к ней нет прямого доступа извне
Возврат публичного интерфейса
Функция возвращает объект с двумя методами:

Эти методы образуют замыкание, сохраняя доступ к startTime
Работа методов
- getElapsedTime():
javascript
return Date.now() - startTime;
- Вычисляет разницу между текущим временем и сохраненным startTime
- Возвращает количество миллисекунд, прошедших с момента создания/сброса таймера
javascript
startTime = Date.now();
- Обновляет startTime текущим временем
- По сути, обнуляет таймер
Особенности работы замыкания
Переменная startTime:
- Существует только в области видимости функции createTimer
- Не доступна напрямую извне
- Сохраняется между вызовами методов благодаря замыканию
При каждом вызове createTimer():
- Создается новое независимое замыкание
- С собственной переменной startTime
Пример использования

Этот пример демонстрирует классическое использование замыканий для:
- Создания инкапсулированного состояния
- Реализации точного таймера
- Предоставления контролируемого интерфейса для работы с приватными данными
Функциональное программирование
Каррирование
Каррирование — это процесс преобразования функции с несколькими аргументами в последовательность функций с одним аргументом. Это мощная техника функционального программирования, реализуемая через замыкания.

Как работает каррирование на примере функции sum
Исходная функция:
javascript
const sum = (a, b, c) => a + b + c;
После каррирования:
javascript
const curriedSum = curry(sum);
Пошаговое выполнение вызова curriedSum(1)(2)(3)
Первый вызов curriedSum(1):
- Получаем аргумент 1 (args = [1])
- Количество аргументов (1) < требуемого (3)
- Возвращается новая функция: (...moreArgs) => curried(1, ...moreArgs)
Второй вызов (2):
- Получаем аргумент 2 (moreArgs = [2])
- Теперь args = [1, 2]
- Количество аргументов (2) < требуемого (3)
- Возвращается новая функция: (...moreArgs) => curried(1, 2, ...moreArgs)
Третий вызов (3):
- Получаем аргумент 3 (moreArgs = [3])
- Теперь args = [1, 2, 3]
- Количество аргументов (3) == требуемому (3)
- Вызывается исходная функция: sum(1, 2, 3)
- Возвращается результат: 6
Преимущества каррирования

Эта реализация каррирования демонстрирует мощь замыканий в JavaScript, позволяя создавать гибкие и переиспользуемые функции.
Мемоизация
Мемоизация — это техника оптимизации, которая сохраняет результаты выполнения функций для предотвращения повторных вычислений при одинаковых входных данных. Это частный случай кэширования.

Как работает мемоизация
Кэширование результатов:
- При первом вызове с определенными аргументами функция выполняется
- Результат сохраняется в Map (ключ - аргументы, значение - результат)
Повторный вызов:
- Если функция вызывается с теми же аргументами
- Результат берется из кэша, без выполнения вычислений
Ключевые особенности реализации
Использование Map:
- Для хранения кэшированных результатов
- Обеспечивает быстрый доступ по ключу
Сериализация аргументов:
- JSON.stringify(args) преобразует аргументы в строку
- Позволяет использовать сложные объекты как ключи
Замыкание:
- Переменная cache сохраняется между вызовами
- Доступна для всех вызовов мемоизированной функции
Пример использования

Ограничения и особенности
Сериализация:
- Не работает с функциями, DOM-элементами и другими несериализуемыми аргументами
- Для объектов важен порядок свойств
Побочные эффекты:
- Не следует применять к функциям с побочными эффектами
- Мемоизированная функция должна быть чистой (идемпотентной)
Размер кэша:
- При долгой работе приложения кэш может расти
- В реальных проектах часто добавляют ограничение по размеру
Практическое применение

Эта реализация мемоизации демонстрирует пример замыканий для оптимизации производительности, сохраняя вычислительно сложные результаты для последующего быстрого доступа.
Паттерны проектирования
Фабрика
Фабрика — это функция, которая создает и возвращает новые объекты. В данном случае мы используем замыкания для создания специализированных фабрик пользователей.

Как работает фабрика пользователей

Ключевые особенности
Использование замыкания:
- Внутренняя функция запоминает параметр role
- При каждом вызове createUserFactory создается новое замыкание
Гибкость:
- Можно создавать фабрики для разных ролей
javascript
const createEditor = createUserFactory(';editor');
const editor = createEditor('Bob');
Инкапсуляция логики:
- Получение прав (getPermissionsForRole) скрыто внутри фабрики
- Клиентский код работает только с интерфейсом
Преимущества подхода
-
Повторное использование:
javascript
const admin1 = createAdmin('Alice');
const admin2 = createAdmin('Carol'); -
Согласованность объектов:
- Все созданные администраторы будут иметь одинаковую структуру
-
Расширяемость:
- Легко добавить новую логику создания пользователей:
- Легко добавить новую логику создания пользователей:
Сравнение с классами
Такой подход альтернативен использованию классов:

Преимущества фабрики:
- Более легковесная реализация
- Лучшая инкапсуляция (нет доступа к this)
- Гибкость в композиции
Практическое применение

Эта реализация фабрики демонстрирует замыкание для создания гибких и переиспользуемых фабрик объектов с сохранением состояния.
Стратегия

Паттерн "Стратегия" позволяет:
- Инкапсулировать семейство алгоритмов
- Делать их взаимозаменяемыми
- Изменять поведение системы на лету без модификации основного кода
Конкретные стратегии:

Использование:

Ключевые особенности реализации

Преимущества подхода
Соблюдение принципов SOLID:
- Open/Closed Principle - новые стратегии добавляются без изменения существующего кода
- Single Responsibility - каждая стратегия отвечает только за свой алгоритм
Упрощение тестирования:
- Каждую стратегию можно тестировать изолированно
- Мокировать стратегии в тестах
Чистая композиция:
- Вместо наследования используется композиция поведения
Расширенный пример с дополнительными возможностями


Расширенный пример с дополнительными возможностями
Практические применения
- Системы оплаты (как в примере)
- Маршрутизация (разные алгоритмы поиска пути)
- Валидация данных (разные стратегии проверки)
- Сортировка данных (разные алгоритмы сортировки)
- Скидочные системы (разные типы скидок)
Эта реализация демонстрирует, как замыкания позволяют элегантно реализовать паттерн "Стратегия", делая код более гибким, расширяемым и удобным для тестирования.
Декораторы в Python
Декораторы — это синтаксический сахар для замыканий, позволяющий модифицировать поведение функций.


Ключевые моменты:
Структура декоратора с параметрами:
- Внешняя функция retry() принимает параметры декоратора
- Функция decorator() принимает целевую функцию
- Функция wrapper() заменяет оригинальную функцию
Механизм повторов:
- Используется цикл while для контроля количества попыток
- try/except перехватывает любые исключения при вызове функции
- После каждой неудачи выводится информационное сообщение
Особенности работы:
- При успешном выполнении функция возвращает результат сразу
- После исчерпания попыток бросается исключение
- Сохраняется оригинальная сигнатура функции благодаря *args, **kwargs
Применение:
- Декоратор можно использовать для любых ненадежных операций
- Особенно полезен для сетевых запросов и операций ввода-вывода
- Количество попыток настраивается при применении декоратора
Такой декоратор значительно повышает надежность кода, работающего с ненадежными ресурсами, автоматизируя обработку временных ошибок.
Ленивые вычисления
Замыкания позволяют откладывать вычисления до момента, когда результат действительно нужен.

Управление состоянием в React (хуки)
Хуки React (useState, useEffect) активно используют замыкания для работы с состоянием.

Замыкания в разных языках программирования
Python
В Python замыкания работают через вложенные функции. Для изменения non-local переменных используется ключевое слово nonlocal.

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

Rust
В Rust замыкания бывают трех типов: Fn, FnMut и FnOnce, в зависимости от того, как они используют захваченные переменные.

Swift
Swift использует замыкания с синтаксисом, похожим на JavaScript. Захваченные переменные можно модифицировать с помощью capture lists.

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

Dart (Flutter)
Dart, язык для Flutter, использует замыкания аналогично другим современным языкам.

Производительность и оптимизация замыканий
- Память: Захваченные переменные не удаляются сборщиком мусора
- Скорость: Современные движки оптимизируют замыкания, но в критичных местах лучше использовать классы
- Утечки памяти: Циклические ссылки через замыкания могут приводить к утечкам
Распространенные ошибки и лучшие практики

Лучшие практики:
- Используйте замыкания для инкапсуляции, а не для хранения больших данных
- Избегайте сложных цепочек замыканий
- Четко разделяйте изменяемое и неизменяемое состояние
Заключение: замыкания как фундаментальный инструмент
Замыкания — это не просто академическая концепция, а мощный инструмент для:
- Создания чистых, модульных интерфейсов
- Реализации сложной бизнес-логики
- Построения эффективных абстракций
- Управления состоянием приложений
Освоив замыкания, вы сможете:
- Писать более выразительный код
- Лучше понимать современные фреймворки
- Эффективнее решать сложные задачи
- Создавать более надежные приложения
Замыкания остаются одной из самых важных концепций, которые должен понимать каждый профессиональный разработчик.
AppFox — ведущая digital-студия с более чем 10-летним опытом в разработке мобильных приложений, игр и VR/AR-решений. Среди клиентов — Mastercard, Сбер, РЖД, Adidas, Ozon и другие. В команде более 100 специалистов. 550+ кейсов.
Хотите обсудить ваш проект?
Свяжитесь с нами для получения бесплатной консультации:
info@appfox.ru
8 800 551 20 99
8 800 551 20 99
https://t.me/AppFoxSales