В React хук useState хранит значение между рендерами компонента — и это работает через замыкания. В Express.js middleware запоминает конфигурацию из момента создания. В любом кэше значение хранится «внутри» функции, недоступное снаружи. Замыкание — это механизм, на котором держится половина JS-экосистемы.
Замыкание — это функция, которая помнит переменные из внешней области видимости даже после того, как внешняя функция завершила выполнение. Функция «захватывает» окружение в момент создания — как фотография.
Переменные видны в той области, где объявлены, и во вложенных функциях:
function outer() {
const secret = 'я снаружи'
function inner() {
console.log(secret) // inner ВИДИТ secret из outer
}
inner() // 'я снаружи'
}
// secret здесь не виднаfunction makeCounter(start = 0) {
let count = start // эта переменная «захвачена»
return function() {
return ++count // count живёт пока живёт возвращённая функция
}
}
const counter = makeCounter(10)
// outer (makeCounter) уже завершилась, но count = 10 помнится
console.log(counter()) // 11
console.log(counter()) // 12 — count в памяти!
console.log(counter()) // 131. Приватное состояние:
function createUser(name, email) {
// эти переменные — «приватные», снаружи недоступны
let _name = name
let _email = email
let _loginCount = 0
return {
login() {
_loginCount++
return `${_name} вошёл (всего: ${_loginCount} раз)`
},
getName: () => _name,
changeName(newName) { _name = newName }
}
}
const user = createUser('Алексей', 'alex@mail.ru')
console.log(user.login()) // 'Алексей вошёл (всего: 1 раз)'
console.log(user.login()) // 'Алексей вошёл (всего: 2 раз)'
// _loginCount недоступен снаружи2. Фабричные функции — создание настроенных функций:
function makeMultiplier(factor) {
return (n) => n * factor // factor захвачен
}
const double = makeMultiplier(2)
const triple = makeMultiplier(3)
console.log(double(5)) // 10
console.log(triple(5)) // 153. Мемоизация — кэширование результатов:
function memoize(fn) {
const cache = new Map() // кэш захвачен замыканием
return function(...args) {
const key = JSON.stringify(args)
if (cache.has(key)) {
console.log('из кэша')
return cache.get(key)
}
const result = fn(...args)
cache.set(key, result)
return result
}
}
const expensiveCalc = memoize((n) => {
// тяжёлые вычисления
return n * n
})
console.log(expensiveCalc(10)) // вычисляет: 100
console.log(expensiveCalc(10)) // из кэша: 100Ошибка 1: замыкание в цикле с var
// Сломано — все коллбэки замыкают ОДНУ переменную i:
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100)
}
// 3, 3, 3 — не 0, 1, 2!
// Исправлено — let создаёт новую переменную на каждую итерацию:
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100)
}
// 0, 1, 2Ошибка 2: утечка памяти через замыкание
// Сломано: крупный массив остаётся в памяти пока живёт коллбэк
function setup() {
const bigData = new Array(1000000).fill('data')
return function() {
return bigData[0] // bigData не освободится пока функция живёт
}
}
// Исправлено: захватывай только то, что нужно
function setup() {
const bigData = new Array(1000000).fill('data')
const firstItem = bigData[0] // копируем нужное
return function() {
return firstItem // bigData может быть собран GC
}
}Ошибка 3: замыкание «живёт» изменением, а не значением
// Замыкание захватывает переменную, а не её значение в момент создания!
let count = 0
const getCount = () => count // захвачена переменная count
count = 42
console.log(getCount()) // 42 — читает текущее значение!useState и useEffect реализованы через замыканияconst authMiddleware = (role) => (req, res, next) => {...}Счётчик запросов API с лимитом и мемоизация
// Фабрика API-клиента с замыканием — приватное состояние
function createApiClient(baseUrl, rateLimit = 10) {
let requestCount = 0
let windowStart = Date.now()
const cache = new Map()
function checkRateLimit() {
const now = Date.now()
// Сброс окна каждую минуту
if (now - windowStart > 60000) {
requestCount = 0
windowStart = now
}
if (requestCount >= rateLimit) {
throw new Error(`Превышен лимит: ${rateLimit} запросов/мин`)
}
requestCount++
}
// Мемоизированный fetch
function cachedGet(path) {
if (cache.has(path)) {
console.log(`[кэш] GET ${path}`)
return cache.get(path)
}
checkRateLimit()
console.log(`[запрос ${requestCount}] GET ${baseUrl}${path}`)
const result = { url: baseUrl + path, timestamp: Date.now() }
cache.set(path, result)
return result
}
return {
get: cachedGet,
getStats: () => ({ requestCount, cacheSize: cache.size }),
clearCache: () => cache.clear(),
}
}
const api = createApiClient('https://api.example.com', 5)
api.get('/users') // [запрос 1] GET https://api.example.com/users
api.get('/products') // [запрос 2] GET https://api.example.com/products
api.get('/users') // [кэш] GET /users — повторный запрос из кэша
console.log(api.getStats()) // { requestCount: 2, cacheSize: 2 }
// У каждого клиента своё состояние
const api2 = createApiClient('https://other.com', 3)
api2.get('/items') // [запрос 1] GET https://other.com/items — независимый счётчик
// Мемоизация тяжёлых вычислений
function memoize(fn) {
const cache = new Map()
return function(...args) {
const key = JSON.stringify(args)
if (!cache.has(key)) cache.set(key, fn.apply(this, args))
return cache.get(key)
}
}
const fibonacci = memoize(function fib(n) {
if (n <= 1) return n
return fibonacci(n - 1) + fibonacci(n - 2)
})
console.log(fibonacci(40)) // 102334155 — быстро благодаря кэшуВ React хук useState хранит значение между рендерами компонента — и это работает через замыкания. В Express.js middleware запоминает конфигурацию из момента создания. В любом кэше значение хранится «внутри» функции, недоступное снаружи. Замыкание — это механизм, на котором держится половина JS-экосистемы.
Замыкание — это функция, которая помнит переменные из внешней области видимости даже после того, как внешняя функция завершила выполнение. Функция «захватывает» окружение в момент создания — как фотография.
Переменные видны в той области, где объявлены, и во вложенных функциях:
function outer() {
const secret = 'я снаружи'
function inner() {
console.log(secret) // inner ВИДИТ secret из outer
}
inner() // 'я снаружи'
}
// secret здесь не виднаfunction makeCounter(start = 0) {
let count = start // эта переменная «захвачена»
return function() {
return ++count // count живёт пока живёт возвращённая функция
}
}
const counter = makeCounter(10)
// outer (makeCounter) уже завершилась, но count = 10 помнится
console.log(counter()) // 11
console.log(counter()) // 12 — count в памяти!
console.log(counter()) // 131. Приватное состояние:
function createUser(name, email) {
// эти переменные — «приватные», снаружи недоступны
let _name = name
let _email = email
let _loginCount = 0
return {
login() {
_loginCount++
return `${_name} вошёл (всего: ${_loginCount} раз)`
},
getName: () => _name,
changeName(newName) { _name = newName }
}
}
const user = createUser('Алексей', 'alex@mail.ru')
console.log(user.login()) // 'Алексей вошёл (всего: 1 раз)'
console.log(user.login()) // 'Алексей вошёл (всего: 2 раз)'
// _loginCount недоступен снаружи2. Фабричные функции — создание настроенных функций:
function makeMultiplier(factor) {
return (n) => n * factor // factor захвачен
}
const double = makeMultiplier(2)
const triple = makeMultiplier(3)
console.log(double(5)) // 10
console.log(triple(5)) // 153. Мемоизация — кэширование результатов:
function memoize(fn) {
const cache = new Map() // кэш захвачен замыканием
return function(...args) {
const key = JSON.stringify(args)
if (cache.has(key)) {
console.log('из кэша')
return cache.get(key)
}
const result = fn(...args)
cache.set(key, result)
return result
}
}
const expensiveCalc = memoize((n) => {
// тяжёлые вычисления
return n * n
})
console.log(expensiveCalc(10)) // вычисляет: 100
console.log(expensiveCalc(10)) // из кэша: 100Ошибка 1: замыкание в цикле с var
// Сломано — все коллбэки замыкают ОДНУ переменную i:
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100)
}
// 3, 3, 3 — не 0, 1, 2!
// Исправлено — let создаёт новую переменную на каждую итерацию:
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100)
}
// 0, 1, 2Ошибка 2: утечка памяти через замыкание
// Сломано: крупный массив остаётся в памяти пока живёт коллбэк
function setup() {
const bigData = new Array(1000000).fill('data')
return function() {
return bigData[0] // bigData не освободится пока функция живёт
}
}
// Исправлено: захватывай только то, что нужно
function setup() {
const bigData = new Array(1000000).fill('data')
const firstItem = bigData[0] // копируем нужное
return function() {
return firstItem // bigData может быть собран GC
}
}Ошибка 3: замыкание «живёт» изменением, а не значением
// Замыкание захватывает переменную, а не её значение в момент создания!
let count = 0
const getCount = () => count // захвачена переменная count
count = 42
console.log(getCount()) // 42 — читает текущее значение!useState и useEffect реализованы через замыканияconst authMiddleware = (role) => (req, res, next) => {...}Счётчик запросов API с лимитом и мемоизация
// Фабрика API-клиента с замыканием — приватное состояние
function createApiClient(baseUrl, rateLimit = 10) {
let requestCount = 0
let windowStart = Date.now()
const cache = new Map()
function checkRateLimit() {
const now = Date.now()
// Сброс окна каждую минуту
if (now - windowStart > 60000) {
requestCount = 0
windowStart = now
}
if (requestCount >= rateLimit) {
throw new Error(`Превышен лимит: ${rateLimit} запросов/мин`)
}
requestCount++
}
// Мемоизированный fetch
function cachedGet(path) {
if (cache.has(path)) {
console.log(`[кэш] GET ${path}`)
return cache.get(path)
}
checkRateLimit()
console.log(`[запрос ${requestCount}] GET ${baseUrl}${path}`)
const result = { url: baseUrl + path, timestamp: Date.now() }
cache.set(path, result)
return result
}
return {
get: cachedGet,
getStats: () => ({ requestCount, cacheSize: cache.size }),
clearCache: () => cache.clear(),
}
}
const api = createApiClient('https://api.example.com', 5)
api.get('/users') // [запрос 1] GET https://api.example.com/users
api.get('/products') // [запрос 2] GET https://api.example.com/products
api.get('/users') // [кэш] GET /users — повторный запрос из кэша
console.log(api.getStats()) // { requestCount: 2, cacheSize: 2 }
// У каждого клиента своё состояние
const api2 = createApiClient('https://other.com', 3)
api2.get('/items') // [запрос 1] GET https://other.com/items — независимый счётчик
// Мемоизация тяжёлых вычислений
function memoize(fn) {
const cache = new Map()
return function(...args) {
const key = JSON.stringify(args)
if (!cache.has(key)) cache.set(key, fn.apply(this, args))
return cache.get(key)
}
}
const fibonacci = memoize(function fib(n) {
if (n <= 1) return n
return fibonacci(n - 1) + fibonacci(n - 2)
})
console.log(fibonacci(40)) // 102334155 — быстро благодаря кэшуРеализуй функцию createRateLimiter(maxCalls, windowMs), которая возвращает функцию-обёртку. Обёртка принимает любую функцию fn и возвращает новую функцию, которая вызывает fn не чаще maxCalls раз за windowMs миллисекунд. Если лимит превышен — выбрасывает ошибку 'Rate limit exceeded'. Используй замыкание для хранения счётчика и времени.
Инициализируй calls = 0, windowStart = Date.now(). Условие сброса: now - windowStart >= windowMs. После сброса: calls = 0, windowStart = now. Затем проверь calls >= maxCalls, увеличь calls++ и вызови fn(...args).