Angularшлях до Strong Junior
Модулі / JavaScript: основа / Область видимості й замикання
🔥 0

Фундамент Ф1 · урок 7 з 12

Область видимості й замикання

Замикання — найчастіше питання про JavaScript на співбесідах. І це не теоретична дивина: на ньому стоять фабрики валідаторів у формах Angular, обробники подій, які «памʼятають» свою задачу, і половина операторів RxJS. Щоб зрозуміти замикання, спершу треба зрозуміти, де змінна «видна», — з цього й почнемо.

Область видимості: де змінну видно

Змінна, оголошена через let чи const, існує лише всередині свого блоку — тобто між фігурними дужками, де її оголосили. Блок функції, if, циклу — кожен створює свою область.

Уяви кімнати з вікнами, через які видно лише назовні. З внутрішньої кімнати видно коридор і двір, а з двору в кімнату — ні. Так і з кодом: зсередини видно змінні зовнішніх блоків, а ззовні внутрішні — ні.

ГЛОБАЛЬНА ОБЛАСТЬ const company = 'Acme'; ФУНКЦІЯ report() const total = 12; БЛОК if const note = `${company}: ${total}`; ✓ звідси видно total і company — назовні а звідси note не видно ✗
Кожна пара фігурних дужок — окрема «кімната». Змінні шукаються від найближчої кімнати назовні, до глобальної області. Всередину чужої кімнати зазирнути не можна.

Запусти й прочитай помилку. Потім перенеси console.log(note) всередину if.

Якщо всередині блоку оголосити змінну з тим самим імʼям, що й назовні, внутрішня затінює зовнішню: у блоці буде видно лише її. Код від цього працює, але читати його важко — даруй змінним різні імена.

Чому не var

У var інші правила: вона бачить не блок, а всю функцію, і «піднімається» на її початок. Через це класичний баг, який трапляється навіть у досвідчених:

setTimeout(fn, 0) відкладає виклик функції «на потім» — після того, як виконається весь поточний код (детально — у Ф2). Вгадай, що виведе кожен цикл, і запусти.

З var існує одна змінна i на всю функцію. Коли відкладені виклики нарешті спрацьовують, цикл уже закінчився, і i дорівнює 3 — усі три функції бачать ту саму трійку. З let кожен крок циклу отримує свою змінну j, і кожна функція бачить своє значення. Звідси правило з першого уроку: var — ніколи.

Замикання: функція з рюкзаком

Тепер головне. Функція памʼятає змінні з місця, де її створили, — навіть коли те місце вже відпрацювало й давно завершилось. Уяви, що кожна функція носить рюкзак, куди склала все, що бачила навколо в момент створення. Це й називають замиканням (closure).

Ключове тут: функція бере в рюкзак не значення, а саму змінну. Тому count накопичується між викликами, а кожен новий виклик makeCounter створює новий рюкзак — і лічильники не заважають один одному.

Зараз обидва лічильники ділять одну глобальну змінну, і b() продовжує рахунок a. Перепиши makeCounter так, щоб кожен лічильник рахував окремо: вивід має бути 1, 2, 1.

Перенеси let count = 0 всередину makeCounter — тоді кожен виклик створить свою змінну, і повернена стрілка забере саме її.

Де замикання трапляються щодня

Фабрики функцій. Функція, яка приймає налаштування й повертає іншу функцію, що їх памʼятає. Саме так зроблено валідатор з параметром у модулі 6:

function maxWords(limit) {
  return (value) => {                          // стрілка памʼятає limit
    const words = value.trim().split(/\s+/).length;
    return words > limit ? { maxWords: limit } : null;
  };
}

const titleCheck = maxWords(12);   // limit = 12 поїхав у рюкзак

Обробники подій. Стрілка в обробнику памʼятає задачу, для якої її створили:

for (const task of tasks) {
  button.addEventListener('click', () => remove(task.id));   // кожна стрілка — зі своїм task
}

Прихований стан. count у makeCounter ззовні не змінити ніяк — лише через повернену функцію. Так у JavaScript ховали дані задовго до приватних полів класу.

Напиши фабрику multiplier(factor): вона повертає функцію, яка множить своє число на factor. Вивід має бути 10 15.

function multiplier(factor) { return (n) => n * factor; }
Типові помилки
  • var у циклі з відкладеними функціями — всі бачать останнє значення. Пиши let.
  • Очікування, що замикання «сфотографувало» значення. Воно памʼятає змінну: змінилась змінна — функція бачить нове.
  • Спроба прочитати змінну з внутрішнього блоку ззовні — ReferenceError: … is not defined.

Підсумок

  • Область видимості — блок у фігурних дужках. Зсередини видно назовні, ззовні всередину — ні.
  • var бачить усю функцію й ламає цикли з відкладеними викликами. let дає кожному кроку свою змінну.
  • Замикання — функція памʼятає змінні місця, де її створили, навіть коли те місце вже відпрацювало.
  • Фабрики (maxWords(12)), обробники з даними, прихований стан — усе це замикання.