Урок 04

Замыкания

Замыкание — функция, которая запоминает переменные из своей внешней области видимости, даже когда внешняя функция уже завершилась.

Простой пример

function outer() {
    const message = 'Привет';

    function inner() {
        console.log(message);
    }

    return inner;
}

const fn = outer();
fn(); // Привет

outer завершилась, но inner всё ещё имеет доступ к message.

Счётчик

function createCounter() {
    let count = 0;

    return {
        increment: () => ++count,
        decrement: () => --count,
        get: () => count,
    };
}

const counter = createCounter();
counter.increment(); // 1
counter.increment(); // 2
counter.get();       // 2

count недоступен снаружи, но живёт между вызовами.

Приватные данные

function createUser(name) {
    let password = '';

    return {
        name,
        setPassword(pwd) {
            password = pwd;
        },
        checkPassword(pwd) {
            return password === pwd;
        },
    };
}

const user = createUser('Алиса');
user.setPassword('secret');
user.checkPassword('secret'); // true
// user.password — undefined

password скрыт. Это эмуляция приватных полей до появления # в классах.

Замыкание в цикле

function makeFunctions() {
    const fns = [];

    for (let i = 0; i < 3; i++) {
        fns.push(() => i);
    }

    return fns;
}

const fns = makeFunctions();
fns[0](); // 0
fns[1](); // 1
fns[2](); // 2

С let каждая итерация имеет свою i.

С var — все функции видели бы одну i = 3.

Функция-множитель

function multiply(a) {
    return (b) => a * b;
}

const double = multiply(2);
const triple = multiply(3);

double(5); // 10
triple(5); // 15

double и triple — замыкания с разными a.

Мемоизация

function memoize(fn) {
    const cache = new Map();

    return (arg) => {
        if (cache.has(arg)) return cache.get(arg);
        const result = fn(arg);
        cache.set(arg, result);
        return result;
    };
}

const slowSquare = (n) => {
    console.log('вычисление');
    return n * n;
};

const fastSquare = memoize(slowSquare);
fastSquare(5); // вычисление, 25
fastSquare(5); // 25, без лога

cache живёт между вызовами.

Обработчики событий

function attachHandlers() {
    const buttons = document.querySelectorAll('button');

    buttons.forEach((button, index) => {
        button.addEventListener('click', () => {
            console.log(`Кнопка ${index}`);
        });
    });
}

Каждый обработчик «запоминает» свой index.

Один раз

function once(fn) {
    let called = false;
    let result;

    return (...args) => {
        if (!called) {
            called = true;
            result = fn(...args);
        }
        return result;
    };
}

const init = once(() => {
    console.log('инициализация');
    return { ready: true };
});

init(); // лог + { ready: true }
init(); // { ready: true }, без лога

Утечки памяти

Замыкания держат переменные в памяти, пока жива функция:

function big() {
    const data = new Array(1000000).fill(0);

    return () => data.length;
}

const fn = big(); // data в памяти

Если функция не нужна — обнули ссылку:

let fn = big();
fn = null; // data освободится

Итоги

  • Замыкание — функция + её внешние переменные
  • Переменные живут, пока жива функция
  • Приватные данные через замыкание
  • let в цикле даёт своё замыкание на итерацию
  • Мемоизация, once, обработчики — на замыканиях
  • Следи за утечками