Найти в Дзене
KARPOV.COURSES | DEV

Замыкания в JS

В кругу начинающих программистов у замыканий сформировалась интересная репутация: все понимают, что эту тему нужно знать, но считают трудной, страшной и чуть ли не мистической.

В этом есть доля правды: замыкания — действительно один из самых мощных и важных инструментов в JavaScript, и владеть им должен каждый JS-программист.

Хорошая новость: если отбросить сложные слова, оказывается, что замыкание — это всего лишь функция, которую возвращает другая функция. Главное — разобраться, как использовать этот инструмент. Но обо всём по порядку.

Чтобы «разгадать» замыкания, для начала разберёмся с понятием «область видимости».

Всё, что вы объявляете в своём коде за пределами функций, условий и циклов*, оказывается в глобальной области видимости. А всё, что создаётся внутри блоков { … }, попадает в их локальную область видимости.

К глобальным переменным, функциям и константам можно обращаться и даже менять их из любой точки программы. Локальные объекты видны только внутри своего «родного» блока кода (в том числе — из вложенных в него дочерних блоков).

Вот пример простого счётчика с шагом два:

let count = 1; // глобальная переменная
function counter() {
let inc = 2; // локальная переменная
count = count + inc; // так можно
return count;
}
count = count + inc; // так нельзя

Здесь из функции counter доступны две переменные: count и inc. Но при этом локальная inc создаётся заново каждый раз, когда функцию вызывают, a глобальная count постоянно существует за её пределами.

В какой-то момент вам может показаться, что удобнее всего объявлять всё в глобальной области и вообще не думать, кому что видно. Это ловушка дьявола! Глобальные переменные делают программу очень уязвимой: ведь ошибка даже в самой незначительной части кода может сломать всё остальное.

Например, если вы используете счётчик неоднократно, то достаточно всего разок забыть обнулить count, чтобы всё испортить. А если вы хотите параллельно запустить несколько независимых счётчиков, вообще придётся писать для каждого новую функцию с новыми переменными.

Вместо этого попробуем усовершенствовать наш код:

function counter() {
let count = 1;
const inc = 2;
return function() {
count = count + inc;
}
}

«Разве такой счётчик не будет заново прибавлять двойку к единице при каждой итерации?» — спросите вы.

Нет, не будет! Всё благодаря тому, что мы возвращаем одну функцию из другой. Это и есть замыкание: дочерняя функция оказывается замкнута на области видимости родительской, то есть продолжает помнить актуальные значения её локальных переменных даже при повторном вызове.

Использовать это можно так:

let counter1 = counter();
let counter2 = counter();

console.log(counter1()); // 3
console.log(counter1()); // 5
console.log(counter2()); // 3
console.log(counter1()); // 7

Обратите внимание, что переменным counter1 и counter2 присваиваются именно функции (да, в JS так можно!) Каждой своя собственная, со своей локальной областью видимости — просто созданная по некоему шаблону. Так обеспечивается независимость двух счётчиков.

Надеемся, что если раньше замыкания казались вам чем-то непостижимым, то сейчас это прошло :)

* для условий и циклов это работает только при использовании let и const — переменные var, объявленные за пределами функций, всегда глобальные