🧠 Оволодіння замиканнями в JavaScript: Посібник від початківця до просунутого рівня

Closures (замикання) — це потужний концепт у JavaScript, який часто зустрічається на інтерв'ю або в складних кодових фрагментах. Хоча вони можуть здатися складними, їх розуміння дає вам доступ до глибших можливостей JavaScript, дозволяючи будувати потужні шаблони, управляти приватним станом та писати чистий, модульний код.

Що таке closure?
Closure виникає, коли функція зберігає доступ до свого лексичного контексту навіть після того, як зовнішня функція завершила своє виконання. Простими словами, closure дозволяє функції "пам'ятати" змінні з місця, де вона була визначена, навіть якщо її викликають в іншому контексті.

Базовий приклад closure:

function outer() {  
 const message = "Hello from outer!";  
 function inner() {  
 console.log(message); // Все ще має доступ до 'message'  
 }  
 return inner;  
}  

const greet = outer(); // outer() виконується, повертає inner()  
greet(); // виводить: Hello from outer!

Цей приклад працює завдяки тому, що функція inner() зберігає доступ до змінної message, навіть після завершення виконання функції outer().

Closure у циклах (поширена пастка на інтерв'ю):

Існує поширений приклад, який може заплутати розробників при роботі з циклом і замиканнями:

for (var i = 0; i < 3; i++) {  
 setTimeout(() => console.log(i), 1000);  
}  
// Виведення: 3, 3, 3

Це відбувається через те, що змінна i змінюється за межами кожної ітерації циклу, і після завершення циклу всі значення будуть рівні 3.

Виправлення за допомогою closure:

Щоб уникнути цієї проблеми, можна створити нову область видимості для кожної ітерації:

for (var i = 0; i < 3; i++) {  
 (function (j) {  
 setTimeout(() => console.log(j), 1000);  
 })(i);  
}  
// Виведення: 0, 1, 2

Тут ми використовуємо IIFE (Immediately Invoked Function Expression), щоб кожен раз захоплювати поточне значення i.

Використання 1: Приватність даних
Closure дозволяє створювати приватні змінні, що неможливо зробити в JavaScript без використання спеціальних механізмів (до введення #private полів). Наприклад, можна створити лічильник з приватною змінною:

function createCounter() {  
 let count = 0;  
 return {  
 increment: () => ++count,  
 get: () => count,  
 };  
}  

const counter = createCounter();  
console.log(counter.increment()); // 1  
console.log(counter.get()); // 1  
console.log(counter.count); // undefined

Змінна count захищена через замикання і недоступна ззовні.

Використання 2: Каррінг / Часткове застосування функцій
Closures роблять можливим каррінг — процес створення нових функцій з частково переданими аргументами. Це можна використовувати для створення більш універсальних функцій:

function multiply(a) {  
 return function (b) {  
 return a * b;  
 };  
}  

const double = multiply(2);  
console.log(double(5)); // 10

Підводний камінь: Closures з var та let
Якщо використовувати let замість var, то кожна ітерація циклу буде створювати нову область видимості, і замикання працюватиме коректно:

for (let i = 0; i < 3; i++) {  
 setTimeout(() => console.log(i), 1000);  
}  
// Виведення: 0, 1, 2

Реальні приклади використання closure:

  1. Дебаунсинг: Використовується для обмеження частоти викликів функцій, таких як обробка подій.
    ~~~
    function debounce(fn, delay) {
    let timeoutId;
    return function () {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => fn.apply(this, arguments), delay);
    };
    }
    ~~~

  2. React useCallback та useMemo: Ці хуки в React використовують closure для запам'ятовування значень між рендерами.

Closures на інтерв'ю:
Замикання часто з'являються на інтерв'ю. Вони можуть бути частиною запитань про область видимості функцій та підняття (hoisting), побудови лічильників з приватними змінними, асинхронну поведінку (наприклад, setTimeout, цикли for), каррінг та часткове застосування функцій, а також реалізацію мемоізації.

Порада для інтерв'ю:
Пояснюючи замикання, скажіть, що це “функції, що пам'ятають середовище, в якому вони були створені.” Показуйте приклади і демонструйте, як ви керуєте областю видимості та пам'яттю.

Підсумок:
Замикання — це важливий концепт у JavaScript, який використовується для інкапсуляції даних, функціонального програмування (каррінг, мемоізація) і уникання асинхронних пасток. Володарювання цими концепціями дасть вам перевагу в написанні чистого та ефективного коду.

Якщо у вас є свій трюк із замиканнями, поділіться ним у коментарях! 👇
📢 Поділіться цим постом, якщо він вам допоміг — давайте разом розвінчувати міфи про JavaScript!

Перекладено з: 🧠 Mastering JavaScript Closures: A Beginner-to-Advanced Guide