Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Користь від closures». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Closure (замикання), це функція, яка пам'ятає лексичне оточення свого створення і має доступ до його змінних навіть тоді, коли зовнішня функція вже завершилась. Завдяки цьому closures дають приватні дані та інкапсуляцію без класів, фабрики функцій з «вшитою» конфігурацією, збереження стану між викликами (лічильники, обробники подій), кешування результатів (memoization) і базові прийоми функційного програмування: currying, partial application, compose. Замикання створюється автоматично щоразу, коли одну функцію оголошують усередині іншої.** ```javascript function makeCounter() { let count = 0; // private state return () => ++count; // the closure remembers it } const next = makeCounter(); next(); // 1 next(); // 2 ``` **Ключове:** closure зберігає посилання на оточення, а не копію значень, тому це готовий механізм для приватного стану, який живе між викликами.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Closure (замикання), це функція, яка «пам'ятає» свою лексичну (зовнішню) область навіть тоді, коли виконується вже поза нею.** Інакше кажучи, функція має доступ до змінних з того місця, де її створили, а не з того, звідки її викликали, і саме на цьому тримаються приватні дані, фабрики функцій, кеші та більшість патернів функційного програмування. ## Теорія ### TL;DR - Closure, це пара «функція + лексичне оточення, у якому її створено». - Внутрішня функція тримає посилання на оточення зовнішньої, тому змінні не зникають після її завершення. - Головні застосування: приватні дані, фабрики функцій, стан між викликами, мемоізація, currying. - Замикання створюється автоматично, робити для цього нічого спеціального не треба. - Closure зберігає посилання на змінну, а не її копію на момент створення. - Зайві закриті посилання тримають пам'ять, тож великі об'єкти в закритому оточенні, це реальний ризик витоку. ### Швидкий приклад ```javascript function outer() { let count = 0; function inner() { count++; console.log(count); } return inner; } const counter = outer(); counter(); // 1 counter(); // 2 counter(); // 3 ``` Що тут відбувається: - `outer()` повертає `inner`; - `inner` запам'ятала змінну `count` зі свого лексичного оточення; - навіть після завершення `outer()` змінна `count` не зникає; - це і є closure, «збережена пам'ять» функції. ### Приватні дані та інкапсуляція Змінні зовнішньої функції недосяжні ззовні, але доступні всім функціям, що в ній створені. Це дає приватний стан без класів і без `#`-полів: ```javascript function createUser(name) { let score = 0; return { getName() { return name; }, addScore() { score++; }, getScore() { return score; } }; } const user = createUser('Oleh'); user.addScore(); user.addScore(); console.log(user.getName(), user.getScore()); // Oleh 2 ``` Тут `score`, це приватна змінна: `user.score` не існує, прочитати чи зіпсувати її ззовні неможливо, але `addScore` і `getScore` мають до неї повний доступ. Так само будують module pattern: назовні віддають лише публічний API, решта лишається у закритому оточенні. ### Фабрики функцій і конфігуровані обробники Closure дозволяє «вшити» конфігурацію у функцію на момент створення: ```javascript function makeMultiplier(factor) { return function(num) { return num * factor; }; } const double = makeMultiplier(2); const triple = makeMultiplier(3); console.log(double(5)); // 10 console.log(triple(5)); // 15 ``` Кожна створена функція має власне значення `factor` і пам'ятає, з яким множником її створили. На тому самому механізмі побудовані currying, partial application і composition: часткове застосування аргументів, це просто повернення функції, що замкнула вже передані значення. ### Збереження стану: обробники подій і мемоізація Обробник події живе довше за функцію, яка його підписала, і продовжує бачити її змінні: ```javascript function setupButton(id) { let clicks = 0; document.getElementById(id).addEventListener('click', () => { clicks++; console.log(`Clicks on button ${id}: ${clicks}`); }); } setupButton('save'); setupButton('cancel'); ``` Кожен обробник пам'ятає власну змінну `clicks`, навіть після виходу з `setupButton`. Той самий прийом дає кеш, який переживає виклики: ```javascript function memoize(fn) { const cache = {}; return function(arg) { if (cache[arg]) { console.log('From cache:', arg); return cache[arg]; } const result = fn(arg); cache[arg] = result; console.log('Computed:', arg); return result; }; } const square = memoize(x => x * x); square(4); // Computed: 4 square(4); // From cache: 4 ``` Об'єкт `cache` існує лише всередині замикання: він недоступний ззовні і не засмічує глобальну область. ### Як це працює під капотом Коли функція створюється, рушій формує для неї лексичне оточення (Lexical Environment), внутрішній об'єкт зі змінними, оголошеними в цій області, і посиланням на зовнішнє оточення. Якщо внутрішня функція використовує змінну ззовні, вона тримає посилання на це оточення, тому збирач сміття не може його прибрати, поки жива сама функція. Саме тому змінні зовнішньої функції доступні й після її завершення. Спеціально нічого робити не треба: замикання виникає щоразу, коли ви оголошуєте функцію всередині іншої. ```javascript function outer() { const a = 42; return function() { console.log(a); // this is a closure }; } ``` ### Де closures використовують на практиці | Сценарій | Приклад | | --- | --- | | Приватні дані | Ховаємо змінні всередині функції, назовні лише методи | | Фабрики функцій | Генератори, конфігуровані обробники | | Модулі та інкапсуляція | Поділ логіки без глобальних змінних | | Кешування (memoization) | Зберігаємо результати обчислень між викликами | | Лічильники та стан | Утримуємо стан між викликами | | Колбеки й обробники подій | Доступ до зовнішніх даних усередині функції | | Функційне програмування | Currying, partial application, compose | ### Типові помилки - **Думати, що closure копіює значення.** Він тримає посилання на змінну: якщо після створення функції змінну змінити, функція побачить нове значення. - **`var` у циклі.** Класична пастка: усі колбеки замикають одну й ту саму змінну і бачать останнє значення. `let` створює нову змінну на кожну ітерацію і розв'язує проблему. - **Ігнорувати пам'ять.** Закрите оточення живе, поки жива функція. Обробник події, що замкнув великий об'єкт або DOM-вузол, не дасть їх зібрати, поки його не відписати. - **Створювати важкі замикання в гарячому коді.** Кожен виклик фабрики створює нове оточення і нову функцію; усередині циклу на мільйон ітерацій це помітно. - **Плутати closure з `this`.** Замикання стосується змінних, `this` визначається способом виклику і закриттям не фіксується (крім стрілкових функцій, які беруть його лексично).Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.