Замыкание — это функция, которая *помнит* переменные из области видимости, где она была определена, даже после того, как эта внешняя область завершила выполнение.
```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(). Баланс не должен быть доступен снаружи напрямую.
Напишите функцию delayedLog(message, seconds), которая выводит сообщение в консоль через заданное количество секунд. Используйте setTimeout и замыкание.
function delayedLog(message, seconds) {
}
delayedLog("hello", 1);
delayedLog("world", 2);
Напишите функцию makeClamp(min, max), которая возвращает функцию. Возвращённая функция принимает число и ограничивает его между min и max включительно.
Напишите функцию 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 с
Для активации режима слепого набора не подсматривайте на физическую клавиатуру.