Замыкание — это функция вместе с лексическим окружением, в котором она была создана. Проще говоря: внутренняя функция "помнит" переменные внешней функции даже после того, как внешняя функция завершила выполнение. Замыкания — фундаментальный механизм JS: без них не было бы приватных переменных, фабричных функций, паттерна модуль и многих других вещей.
Каждая функция при создании сохраняет ссылку на **лексическое окружение** — объект со всеми переменными, доступными в том месте, где функция была написана в коде.
function outer() {
const secret = 42 // переменная внешней функции
function inner() {
// inner "видит" secret, потому что была создана внутри outer
console.log(secret) // 42
}
return inner
}
const fn = outer() // outer выполнилась и "закончилась"
fn() // но inner всё ещё помнит secret!inner — это замыкание. Она захватила переменную secret из лексического окружения outer.
function createCounter(initialValue = 0) {
let count = initialValue // приватная переменная
return {
increment() { return ++count },
decrement() { return --count },
reset() { count = initialValue; return count },
getValue() { return count },
}
}
const counter = createCounter(10)
console.log(counter.getValue()) // 10
console.log(counter.increment()) // 11
console.log(counter.increment()) // 12
console.log(counter.decrement()) // 11
console.log(counter.reset()) // 10
// count недоступна снаружи — это и есть инкапсуляция!
console.log(counter.count) // undefinedfunction makeMultiplier(factor) {
// factor "запоминается" каждым созданным умножителем
return (number) => number * factor
}
const double = makeMultiplier(2)
const triple = makeMultiplier(3)
const times10 = makeMultiplier(10)
console.log(double(5)) // 10
console.log(triple(5)) // 15
console.log(times10(5)) // 50
// Каждая функция — отдельное замыкание со своим factorfunction memoize(fn) {
const cache = {} // кеш "живёт" в замыкании
return function(...args) {
const key = JSON.stringify(args)
if (key in cache) {
console.log('Из кеша:', key)
return cache[key]
}
const result = fn(...args)
cache[key] = result
return result
}
}
const expensiveCalc = memoize((n) => {
// имитация долгого вычисления
return n * n
})
console.log(expensiveCalc(10)) // вычисляет: 100
console.log(expensiveCalc(10)) // из кеша: 100
console.log(expensiveCalc(20)) // вычисляет: 400Это **самый популярный вопрос** на собеседованиях про замыкания:
// БАग: var имеет function scope, все колбэки замкнуты на ОДНУ переменную i
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100)
}
// Выводит: 3, 3, 3 (НЕ 0, 1, 2!)
// Почему? К моменту выполнения setTimeout, цикл уже завершился и i = 3
// ИСПРАВЛЕНИЕ 1: let (block scope — каждая итерация имеет свой i)
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100)
}
// Выводит: 0, 1, 2
// ИСПРАВЛЕНИЕ 2: IIFE (немедленно вызываемая функция создаёт новую область)
for (var i = 0; i < 3; i++) {
;(function(j) {
setTimeout(() => console.log(j), 100)
})(i)
}
// Выводит: 0, 1, 2const shoppingCart = (function() {
// Приватные данные — недоступны снаружи
const items = []
let discount = 0
// Публичный API
return {
addItem(item) { items.push(item) },
removeItem(name) {
const idx = items.findIndex(i => i.name === name)
if (idx !== -1) items.splice(idx, 1)
},
setDiscount(pct) { discount = pct },
getTotal() {
const subtotal = items.reduce((sum, item) => sum + item.price, 0)
return subtotal * (1 - discount / 100)
},
getItems() { return [...items] }, // возвращаем копию, не оригинал
}
})()
shoppingCart.addItem({ name: 'Книга', price: 500 })
shoppingCart.addItem({ name: 'Курс', price: 2000 })
shoppingCart.setDiscount(10)
console.log(shoppingCart.getTotal()) // 2250
console.log(shoppingCart.items) // undefined — приватно!Замыкания хранят ссылки на внешние переменные → переменные не удаляются сборщиком мусора. Будь осторожен с замыканиями в обработчиках событий — всегда удаляй их при уничтожении компонента.
**Начни с определения**: "Замыкание — это функция, которая помнит своё лексическое окружение. Даже после завершения внешней функции, внутренняя сохраняет доступ к переменным."
**Сразу покажи пример**: createCounter() — самый чистый и понятный пример. Показывает приватное состояние и практическую ценность.
**Упомяни классический баг**: var в цикле с setTimeout. Это обязательная часть ответа — любой опытный интервьюер спросит об этом.
**Практическая ценность**: упомяни мемоизацию, паттерн модуль, фабричные функции.
1. **"Замыкание — это когда функция внутри функции"** — это описание синтаксиса, не определение. Главное — захват переменных из внешнего окружения.
2. **Не знать классический баг с var в цикле** — это проверяется на 90% собеседований. Незнание выдаёт поверхностное понимание.
3. **Не понимать практических применений** — если ты знаешь что такое замыкание, но не можешь назвать зачем оно нужно — это слабый ответ.
createCounter, makeMultiplier и memoize — три классических примера замыканий на собеседовании
// ===== 1. СЧЁТЧИК: приватное состояние =====
function createCounter(start = 0, step = 1) {
let count = start // приватная переменная
return {
next() { count += step; return count },
prev() { count -= step; return count },
reset() { count = start; return count },
current() { return count },
// Метод для создания нового счётчика с текущим значением
clone() { return createCounter(count, step) }
}
}
const counter = createCounter(0, 2) // начинаем с 0, шаг 2
console.log(counter.next()) // 2
console.log(counter.next()) // 4
console.log(counter.next()) // 6
console.log(counter.current()) // 6
console.log(counter.reset()) // 0
const clone = counter.clone() // клон с текущим состоянием (0)
console.log(clone.next()) // 2 (независимый счётчик)
console.log(counter.next()) // 2 (оригинал тоже на 2)
// ===== 2. ФАБРИЧНАЯ ФУНКЦИЯ =====
function makeMultiplier(factor) {
return (n) => n * factor // захватывает factor
}
const double = makeMultiplier(2)
const triple = makeMultiplier(3)
// Каждая функция — отдельное замыкание
console.log('\ndouble(7):', double(7)) // 14
console.log('triple(7):', triple(7)) // 21
// Применение к массиву
const nums = [1, 2, 3, 4, 5]
console.log('doubled:', nums.map(double)) // [2, 4, 6, 8, 10]
console.log('tripled:', nums.map(triple)) // [3, 6, 9, 12, 15]
// ===== 3. МЕМОИЗАЦИЯ =====
function memoize(fn) {
const cache = new Map() // Map живёт в замыкании
return function(...args) {
const key = JSON.stringify(args)
if (cache.has(key)) {
return cache.get(key)
}
const result = fn.apply(this, args)
cache.set(key, result)
return result
}
}
// Fibonacci без мемоизации: O(2^n)
function fib(n) {
if (n <= 1) return n
return fib(n - 1) + fib(n - 2)
}
// С мемоизацией: O(n)
const fastFib = memoize(function self(n) {
if (n <= 1) return n
return self(n - 1) + self(n - 2)
})
console.log('\nfib(10):', fastFib(10)) // 55
console.log('fib(20):', fastFib(20)) // 6765
console.log('fib(30):', fastFib(30)) // 832040
// ===== 4. КЛАССИЧЕСКИЙ БАГ С var =====
console.log('\n=== Баг с var в цикле ===')
// БАГ
const buggyFns = []
for (var i = 0; i < 3; i++) {
buggyFns.push(() => i)
}
// К этому моменту i = 3 (loop завершился)
console.log('С var:', buggyFns.map(f => f())) // [3, 3, 3]
// ИСПРАВЛЕНИЕ с let
const fixedFns = []
for (let j = 0; j < 3; j++) {
fixedFns.push(() => j) // каждая итерация — своя переменная j
}
console.log('С let:', fixedFns.map(f => f())) // [0, 1, 2]Замыкание — это функция вместе с лексическим окружением, в котором она была создана. Проще говоря: внутренняя функция "помнит" переменные внешней функции даже после того, как внешняя функция завершила выполнение. Замыкания — фундаментальный механизм JS: без них не было бы приватных переменных, фабричных функций, паттерна модуль и многих других вещей.
Каждая функция при создании сохраняет ссылку на **лексическое окружение** — объект со всеми переменными, доступными в том месте, где функция была написана в коде.
function outer() {
const secret = 42 // переменная внешней функции
function inner() {
// inner "видит" secret, потому что была создана внутри outer
console.log(secret) // 42
}
return inner
}
const fn = outer() // outer выполнилась и "закончилась"
fn() // но inner всё ещё помнит secret!inner — это замыкание. Она захватила переменную secret из лексического окружения outer.
function createCounter(initialValue = 0) {
let count = initialValue // приватная переменная
return {
increment() { return ++count },
decrement() { return --count },
reset() { count = initialValue; return count },
getValue() { return count },
}
}
const counter = createCounter(10)
console.log(counter.getValue()) // 10
console.log(counter.increment()) // 11
console.log(counter.increment()) // 12
console.log(counter.decrement()) // 11
console.log(counter.reset()) // 10
// count недоступна снаружи — это и есть инкапсуляция!
console.log(counter.count) // undefinedfunction makeMultiplier(factor) {
// factor "запоминается" каждым созданным умножителем
return (number) => number * factor
}
const double = makeMultiplier(2)
const triple = makeMultiplier(3)
const times10 = makeMultiplier(10)
console.log(double(5)) // 10
console.log(triple(5)) // 15
console.log(times10(5)) // 50
// Каждая функция — отдельное замыкание со своим factorfunction memoize(fn) {
const cache = {} // кеш "живёт" в замыкании
return function(...args) {
const key = JSON.stringify(args)
if (key in cache) {
console.log('Из кеша:', key)
return cache[key]
}
const result = fn(...args)
cache[key] = result
return result
}
}
const expensiveCalc = memoize((n) => {
// имитация долгого вычисления
return n * n
})
console.log(expensiveCalc(10)) // вычисляет: 100
console.log(expensiveCalc(10)) // из кеша: 100
console.log(expensiveCalc(20)) // вычисляет: 400Это **самый популярный вопрос** на собеседованиях про замыкания:
// БАग: var имеет function scope, все колбэки замкнуты на ОДНУ переменную i
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100)
}
// Выводит: 3, 3, 3 (НЕ 0, 1, 2!)
// Почему? К моменту выполнения setTimeout, цикл уже завершился и i = 3
// ИСПРАВЛЕНИЕ 1: let (block scope — каждая итерация имеет свой i)
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100)
}
// Выводит: 0, 1, 2
// ИСПРАВЛЕНИЕ 2: IIFE (немедленно вызываемая функция создаёт новую область)
for (var i = 0; i < 3; i++) {
;(function(j) {
setTimeout(() => console.log(j), 100)
})(i)
}
// Выводит: 0, 1, 2const shoppingCart = (function() {
// Приватные данные — недоступны снаружи
const items = []
let discount = 0
// Публичный API
return {
addItem(item) { items.push(item) },
removeItem(name) {
const idx = items.findIndex(i => i.name === name)
if (idx !== -1) items.splice(idx, 1)
},
setDiscount(pct) { discount = pct },
getTotal() {
const subtotal = items.reduce((sum, item) => sum + item.price, 0)
return subtotal * (1 - discount / 100)
},
getItems() { return [...items] }, // возвращаем копию, не оригинал
}
})()
shoppingCart.addItem({ name: 'Книга', price: 500 })
shoppingCart.addItem({ name: 'Курс', price: 2000 })
shoppingCart.setDiscount(10)
console.log(shoppingCart.getTotal()) // 2250
console.log(shoppingCart.items) // undefined — приватно!Замыкания хранят ссылки на внешние переменные → переменные не удаляются сборщиком мусора. Будь осторожен с замыканиями в обработчиках событий — всегда удаляй их при уничтожении компонента.
**Начни с определения**: "Замыкание — это функция, которая помнит своё лексическое окружение. Даже после завершения внешней функции, внутренняя сохраняет доступ к переменным."
**Сразу покажи пример**: createCounter() — самый чистый и понятный пример. Показывает приватное состояние и практическую ценность.
**Упомяни классический баг**: var в цикле с setTimeout. Это обязательная часть ответа — любой опытный интервьюер спросит об этом.
**Практическая ценность**: упомяни мемоизацию, паттерн модуль, фабричные функции.
1. **"Замыкание — это когда функция внутри функции"** — это описание синтаксиса, не определение. Главное — захват переменных из внешнего окружения.
2. **Не знать классический баг с var в цикле** — это проверяется на 90% собеседований. Незнание выдаёт поверхностное понимание.
3. **Не понимать практических применений** — если ты знаешь что такое замыкание, но не можешь назвать зачем оно нужно — это слабый ответ.
createCounter, makeMultiplier и memoize — три классических примера замыканий на собеседовании
// ===== 1. СЧЁТЧИК: приватное состояние =====
function createCounter(start = 0, step = 1) {
let count = start // приватная переменная
return {
next() { count += step; return count },
prev() { count -= step; return count },
reset() { count = start; return count },
current() { return count },
// Метод для создания нового счётчика с текущим значением
clone() { return createCounter(count, step) }
}
}
const counter = createCounter(0, 2) // начинаем с 0, шаг 2
console.log(counter.next()) // 2
console.log(counter.next()) // 4
console.log(counter.next()) // 6
console.log(counter.current()) // 6
console.log(counter.reset()) // 0
const clone = counter.clone() // клон с текущим состоянием (0)
console.log(clone.next()) // 2 (независимый счётчик)
console.log(counter.next()) // 2 (оригинал тоже на 2)
// ===== 2. ФАБРИЧНАЯ ФУНКЦИЯ =====
function makeMultiplier(factor) {
return (n) => n * factor // захватывает factor
}
const double = makeMultiplier(2)
const triple = makeMultiplier(3)
// Каждая функция — отдельное замыкание
console.log('\ndouble(7):', double(7)) // 14
console.log('triple(7):', triple(7)) // 21
// Применение к массиву
const nums = [1, 2, 3, 4, 5]
console.log('doubled:', nums.map(double)) // [2, 4, 6, 8, 10]
console.log('tripled:', nums.map(triple)) // [3, 6, 9, 12, 15]
// ===== 3. МЕМОИЗАЦИЯ =====
function memoize(fn) {
const cache = new Map() // Map живёт в замыкании
return function(...args) {
const key = JSON.stringify(args)
if (cache.has(key)) {
return cache.get(key)
}
const result = fn.apply(this, args)
cache.set(key, result)
return result
}
}
// Fibonacci без мемоизации: O(2^n)
function fib(n) {
if (n <= 1) return n
return fib(n - 1) + fib(n - 2)
}
// С мемоизацией: O(n)
const fastFib = memoize(function self(n) {
if (n <= 1) return n
return self(n - 1) + self(n - 2)
})
console.log('\nfib(10):', fastFib(10)) // 55
console.log('fib(20):', fastFib(20)) // 6765
console.log('fib(30):', fastFib(30)) // 832040
// ===== 4. КЛАССИЧЕСКИЙ БАГ С var =====
console.log('\n=== Баг с var в цикле ===')
// БАГ
const buggyFns = []
for (var i = 0; i < 3; i++) {
buggyFns.push(() => i)
}
// К этому моменту i = 3 (loop завершился)
console.log('С var:', buggyFns.map(f => f())) // [3, 3, 3]
// ИСПРАВЛЕНИЕ с let
const fixedFns = []
for (let j = 0; j < 3; j++) {
fixedFns.push(() => j) // каждая итерация — своя переменная j
}
console.log('С let:', fixedFns.map(f => f())) // [0, 1, 2]Исправь классический баг с замыканием в цикле, а затем реализуй функцию createLogger — фабрику логгеров с приватным префиксом и счётчиком сообщений.
Для исправления бага замени var на let. Для createLogger: все методы замкнуты на переменную count. warn увеличивает тот же счётчик. getCount() возвращает count. reset() устанавливает count = 0.
Токены для AI-помощника закончились
Купи токены чтобы задавать вопросы AI прямо в уроке