Замикання — це функція, яка *пам'ятає* змінні зі сфери видимості, де вона була визначена, навіть після того, як ця зовнішня сфера завершила виконання.
```javascript
function makeCounter() {
let count = 0; // зовнішня змінна
return function () { // внутрішня функція — замикання
count += 1;
return count;
};
}
const counter = makeCounter();
counter(); // 1
counter(); // 2
counter(); // 3
```
Після повернення `makeCounter()` можна було б очікувати, що `count` зникне. Але `counter` все ще зберігає посилання на нього — це і є замикання.
**Ланцюжок областей видимості**
Коли функція шукає змінну, вона проходить ланцюжок областей видимості: локальна → функція, що охоплює → область модуля → глобальна. Замикання захоплює зовнішню область у момент *визначення* внутрішньої функції, а не виклику.
```javascript
const x = 'global';
function outer() {
const x = 'outer';
function inner() {
console.log(x); // 'outer' — захоплено з охоплюючої области
}
return inner;
}
const fn = outer();
fn(); // 'outer' (не 'global')
```
**Замикання vs глобальна змінна**
Обидва зберігають стан між викликами. Різниця: замикання тримає стан *приватним* для функції, яка його створила, тоді як глобальна змінна доступна всьому.
```javascript
// Глобальний стан - будь-хто може випадково змінити його
let globalCount = 0;
function incrementGlobal() { globalCount++; return globalCount; }
// Стан замикання - лише повернена функція може його змінити
function makeSecureCounter() {
let count = 0;
return () => ++count;
}
const secureCount = makeSecureCounter();
// count недоступний ззовні
```
**Кілька замикань, що поділяють ту саму зовнішню змінну**
Два замикання, створені в одній области видимості, поділяють *те саме* прив'язування, а не незалежні копії:
```javascript
function makeUpDown() {
let n = 0;
return {
up: () => ++n,
down: () => --n,
get: () => n,
};
}
const counter = makeUpDown();
counter.up(); // 1
counter.up(); // 2
counter.down(); // 1
counter.get(); // 1 — і up, і down бачать той самий n
```
Функції-фабрики, помилка var у циклі та патерн модуля
**Функції-фабрики**
Фабрика — це функція, яка створює та повертає інші функції. Кожна повернена функція замикається на власному приватному стані:
```javascript
function makeAdder(x) {
return (y) => x + y; // x захоплено
}
const add5 = makeAdder(5);
const add10 = makeAdder(10);
add5(3) // 8
add10(3) // 13 — незалежний від add5, має власний x
```
**Класична помилка з `var` у циклі**
Це одна з найвідоміших пасток JavaScript. Всі замикання у циклі поділяють *те саме* прив'язування `var i`, яке після завершення циклу стає 3:
```javascript
const fns = [];
for (var i = 0; i < 3; i++) {
fns.push(() => i); // всі замикаються на одному 'i'
}
fns[0](); // 3 (не 0!)
fns[1](); // 3
fns[2](); // 3
```
**Виправлення 1: використати `let`** — `let` створює нове прив'язування для кожної ітерації:
```javascript
const fns = [];
for (let i = 0; i < 3; i++) {
fns.push(() => i); // кожна ітерація має власний 'i'
}
fns[0](); // 0
fns[1](); // 1
fns[2](); // 2
```
**Виправлення 2: IIFE** (функція, що негайно викликається) — захоплює значення у той момент:
```javascript
const fns = [];
for (var i = 0; i < 3; i++) {
fns.push(((captured) => () => captured)(i));
}
fns[0](); // 0
```
Рішення з IIFE — історичне, до появи `let` (ES6). Сьогодні завжди використовуй `let` або `const` у циклах.
**Патерн модуля**
До модулів ES6 замикання використовувалися для приватного стану: IIFE повертає об'єкт з публічними методами, залишаючи деталі реалізації прихованими.
```javascript
const bankAccount = (() => {
let balance = 0; // приватне
return {
deposit: (n) => { balance += n; },
withdraw: (n) => { balance = Math.max(0, balance - n); },
getBalance: () => balance,
};
})();
bankAccount.deposit(100);
bankAccount.withdraw(30);
bankAccount.getBalance(); // 70
// balance недоступний напряму
```
Патерни замикань: мемоїзація, once, partial та пастки пам'яті
Напишіть функцію makeCounter(), яка повертає новий об'єкт-лічильник. Кожен виклик makeCounter() має створювати незалежний лічильник з методами increment() і getValue().
Напишіть функцію once(fn), яка приймає функцію і повертає нову функцію, що може бути викликана лише один раз. При першому виклику вона виконує fn і повертає результат; наступні виклики повертають undefined.
Напишіть функцію memoize(fn), яка обгортає функцію і кешує її результати. Якщо функцію знову викликають з тим самим аргументом, поверніть кешований результат замість повторного виклику fn.
function memoize(fn) {
}
const slowSquare = memoize(n => {
console.log(`computing ${n}...`);
return n * n;
});
console.log(slowSquare(4));
console.log(slowSquare(4));
console.log(slowSquare(5));
Рішення
function memoize(fn) {
const cache = {};
return function(arg) {
if (arg in cache) return cache[arg];
cache[arg] = fn(arg);
return cache[arg];
};
}
const slowSquare = memoize(n => {
console.log(`computing ${n}...`);
return n * n;
});
console.log(slowSquare(4));
console.log(slowSquare(4));
console.log(slowSquare(5));
Напишіть функцію makeBankAccount(initialBalance), яка повертає об'єкт з методами deposit(amount), withdraw(amount) і getBalance(). Баланс не повинен бути доступний ззовні безпосередньо.
Напишіть функцію withCallCount(fn), яка обгортає функцію і повертає об'єкт з методом call і методом getCount(). call() викликає оригінальну функцію, а getCount() повертає кількість викликів.
function withCallCount(fn) {
}
const wrapped = withCallCount(x => x * 2);
wrapped.call(5);
wrapped.call(10);
wrapped.call(3);
console.log(wrapped.getCount());
Напишіть функцію partial(fn, ...preArgs), яка приймає функцію і деякі аргументи та повертає нову функцію, яка при виклику підставляє ці аргументи першими до fn.
function partial(fn, ...preArgs) {
}
function add(a, b, c) {
return a + b + c;
}
const add10 = partial(add, 10);
console.log(add10(5, 3));
console.log(add10(1, 2));
Рішення
function partial(fn, ...preArgs) {
return function(...laterArgs) {
return fn(...preArgs, ...laterArgs);
};
}
function add(a, b, c) {
return a + b + c;
}
const add10 = partial(add, 10);
console.log(add10(5, 3));
console.log(add10(1, 2));
No split tab
Налаштування cookies
Ми використовуємо необхідні cookies для роботи сайту. З вашого дозволу ми також можемо зберігати налаштування сайту та використовувати аналітичні й рекламні cookies, щоб розуміти використання сайту й підтримувати розвиток проєкту.
* Ви завжди можете змінити свій вибір у налаштуваннях сайту.
Оберіть категорії cookies
Налаштування аналітики
Можна вимкнути аналітику використання платформи. Також можна надіслати в Google Analytics запит на видалення даних про використання цього сайту, пов'язаних із цим браузером.
Навчальний workspace
Навчайтеся, читаючи, запускаючи код і розв'язуючи задачі.
Практикуйте програмування з поясненнями тем, вправами, інструментами browser IDE, перевіркою regex і тренуванням друку коду в одному workspace.
Відкривайте інструменти у вкладках.Вправи, IDE-інструменти й тренажери залишаються доступними як вкладки сайту.
Перемикайтеся без втрати контексту.Переходьте між поясненнями, кодом та утилітами, зберігаючи своє місце.
Використовуйте sidebar як карту.Ліві панелі містять навігацію, налаштування, файли, libraries та керування інструментами.
PythonJavaScriptSQLite
Одна IDE, три практичні режими
Python у браузері.Запускайте невеликі скрипти, пробуйте бібліотеки й тренуйте API-запити без встановлення.
JavaScript для швидких експериментів.Перевіряйте код для браузера й порівнюйте ідеї поруч із навчальними матеріалами.
SQLite для практики з даними.Відкривайте оглядач бази даних, переглядайте таблиці, пишіть запити й вивчайте SQL локально.
ТемаIDE
Працюйте поруч у split tabs
Тримайте інструкції перед очима.Відкрийте вправу або довідкову сторінку поруч з IDE, замість постійних перемикань.
Порівнюйте інструменти під час навчання.Розміщуйте перевірки regex, пояснення та експерименти з кодом поруч, коли це потрібно для задачі.
Закрийте split, коли завершите.Workspace повернеться до однієї сфокусованої вкладки, а відкриті вкладки сайту залишаться доступними.
Тренажер друку коду
Або звичайного тексту
Тренажер розрахований на фізичну клавіатуру.Відкрийте цей розділ на ноутбуці або комп'ютері з широким екраном. На телефоні тренування друку не працюватиме коректно.
Швидкість: 0 зн/хв
0 слів/хв
Найкраща швидкість (60с): 0 зн/хв
0 слів/хв
Помилки: 0
Загальний час: 0.0 с
Щоб тренувати сліпий друк, не підглядайте на фізичну клавіатуру.