W3docs

Замыкания

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

Понимание замыканий в JavaScript

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

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

Замыкание — это комбинация функции и лексического окружения, в котором эта функция была объявлена. Окружение состоит из всех локальных переменных, которые находились в области видимости на момент создания замыкания.

Пример замыкания

Рассмотрим следующий пример:

javascript— editable

В этом примере greetByName является замыканием. Оно захватывает переменные greeting и name из области видимости функции greet.

Когда функция greet вызывается с аргументом 'John', она создаёт локальную переменную greeting и функцию greetByName. Затем функция greet возвращает функцию greetByName. Даже после завершения выполнения greet возвращённая функция greetByName всё ещё имеет доступ к переменным greeting и name. Это происходит потому, что greetByName является замыканием — оно помнит окружение, в котором было создано.

Зачем использовать замыкания?

Замыкания обеспечивают инкапсуляцию данных и создание приватных переменных. Они позволяют функциям сохранять доступ к переменным из объемлющей (охватывающей) области видимости даже после того, как эта область закончила выполнение.

Создание простого замыкания

Рассмотрим следующий пример:

javascript— editable

Когда вызывается outerFunction, она создаёт outerVariable и innerFunction, а затем возвращает innerFunction. Возвращённая innerFunction сохраняет доступ к outerVariable даже после завершения выполнения outerFunction.

Практические случаи применения замыканий

Приватность данных

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

javascript— editable

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

Важный нюанс: каждый вызов createCounter создаёт собственное независимое замыкание со своим приватным count. Два счётчика, созданных отдельно, не разделяют состояние:

javascript— editable

Каждый вызов внешней функции создаёт совершенно новое лексическое окружение, поэтому a и b увеличивают разные переменные.

Фабрики функций

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

javascript— editable

Здесь createMultiplier создаёт функции, умножающие заданное число на указанный multiplier. Каждая возвращённая функция сохраняет доступ к значению multiplier через замыкание.

Замыкания в циклах

Замыкания могут быть каверзными при использовании внутри циклов. Рассмотрим следующий пример:

javascript— editable

В этом коде цикл завершается, а затем все три коллбэка setTimeout выполняются, выводя значение i, которое равно 4 после окончания цикла. Это происходит потому, что var имеет функциональную область видимости.

Для исправления используйте let с блочной областью видимости:

javascript— editable
Информация

Всегда используйте let или const вместо var, чтобы избежать проблем с областью видимости и обеспечить переменным надлежащий охват.

Почему работает версия с let

Ключевое различие в том, что let создаёт новую привязку i для каждой итерации цикла. Каждый коллбэк setTimeout замыкается над своим i и запоминает значение, существовавшее во время своей итерации. При использовании var существует лишь одна переменная i, общая для всех коллбэков, и к моменту срабатывания таймеров цикл уже увеличил её до 4.

До появления let классическим решением было создание отдельной области видимости для каждой итерации с помощью IIFE:

javascript— editable

Немедленно вызываемая функция копирует текущее значение i в свой параметр captured, давая каждому коллбэку собственную приватную копию для замыкания.

Замыкания и управление памятью

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

Внимание

Следите за использованием памяти при создании замыканий. Убедитесь, что замыкания не захватывают без необходимости крупные объекты или элементы DOM, чтобы предотвратить утечки памяти.

Продвинутые паттерны замыканий

Паттерн модуля

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

javascript— editable

В этом примере CounterModule — это немедленно вызываемое функциональное выражение (IIFE), которое возвращает объект с публичными методами (increment и reset). Переменная count остаётся приватной и недоступна напрямую.

Замыкания в современных JavaScript-фреймворках

Замыкания особенно важны в современных JavaScript-фреймворках, таких как React. В React замыкания помогают управлять состоянием и побочными эффектами в функциональных компонентах.

import React, { useState } from 'react';

function Counter() {
    const [count, setCount] = useState(0);

    function increment() {
        setCount(prevCount => prevCount + 1);
    }

    return (
        <div>
            <p>{count}</p>
            <button onClick={increment}>Increment</button>
        </div>
    );
}

В этом примере useState используется для создания переменной состояния count и функции setCount для её обновления. Функция increment образует замыкание, захватывая setCount и count из области видимости компонента. Примечание: в React замыкания иногда могут захватывать устаревшее состояние, если зависимости не управляются должным образом в хуках вроде useEffect.

Информация

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

Лучшие практики использования замыканий

  1. Ограничивайте область видимости: избегайте создания замыканий внутри циклов или глубоко вложенных структур без необходимости, чтобы предотвратить утечки памяти и проблемы с производительностью.
  2. Минимизируйте захватываемые переменные: захватывайте в замыкании только те переменные, которые действительно нужны, чтобы снизить потребление памяти.
  3. Избегайте захвата крупных объектов: не захватывайте крупные объекты или элементы DOM в замыканиях, чтобы не удерживать память излишне.
  4. Используйте let и const: всегда предпочитайте let или const вместо var для корректной области видимости и предотвращения непредвиденного поведения.
  5. Очищайте ресурсы: по возможности очищайте ссылки, захваченные замыканиями, чтобы избежать утечек памяти, особенно в долгоживущих приложениях.

Заключение

Замыкания — мощная возможность JavaScript, обеспечивающая приватность данных, фабрики функций и продвинутые паттерны вроде модулей. Понимание и эффективное использование замыканий ведёт к созданию более надёжного и поддерживаемого кода. Освоив замыкания, вы улучшите свою способность писать эффективный, безопасный и чистый JavaScript-код.

Практика

Практика
Что такое замыкания в JavaScript?
Что такое замыкания в JavaScript?
Практика
Почему цикл 'for' с 'var' и setTimeout выводит одно и то же конечное значение три раза, тогда как 'let' выводит 1, 2, 3?
Почему цикл 'for' с 'var' и setTimeout выводит одно и то же конечное значение три раза, тогда как 'let' выводит 1, 2, 3?
Was this page helpful?