DEV Community

Matheus de Gondra
Matheus de Gondra

Posted on

Closures Javascript

Escopo

O javascript trabalha com escopo. Vamos pegar o código abaixo:

// Escopo do arquivo
const name = "Fulano";
const age = 25;

const foo = () => {
    // Escopo da função

    const contador = 10;

    console.log(`Olá ${name}, você tem ${age} anos.`);
}

foo();
Enter fullscreen mode Exit fullscreen mode

Esse código possui 3 escopos ou contextos: escopo global, escopo de arquivo (módulo) e o escopo da função foo.

Escopos

Se colocarmos um breakpoint na execução da função foo podemos rodar o código em modo de debug e usar o painel de debug do vscode para ver isso.

Na esquerda, no painel VARIABLES, podemos observar 2 escopos definidos: Module que é o escopo do arquivo, e Global que é o escopo global onde ficam variáveis e funções globais do javascript.

Agora vamos colocar um breakpoint no console.log dentro da função foo. Vamos ver que um novo escopo será criado quando o código chegar dentro da função.

Esse Local: foo é o escopo da função foo e dentro dele existe a variável contador.

No javascript o escopo interno tem acesso ao escopo externo, mas o contrario não.

O que são Closures?

Uma closure é a combinação de uma função com as referências ao estado que a circunda (o ambiente léxico).

Essa é a definição de closure no site do MDN. Explicando melhor o que seria isso, é uma função que consegue guardar o estado do escopo onde ela foi definida.

const externa = () => {
    let contador = 0;

    // interna é uma closure, pois ela consegue salvar o escopo de externa mesmo ao fim dela
    const interna = () => {
        contador++;
        console.log(contador);
    }

    return interna;
}

const funcaoInterna = externa(); // retorna a função interna, que é uma closure

funcaoInterna(); // 1
funcaoInterna(); // 2
funcaoInterna(); // 3
Enter fullscreen mode Exit fullscreen mode

Se eu colocar um breakpoint no contador++, no painel de debug terá o escopo Local: interna que é o escopo da função interna e Closure (externa) que é o escopo que a função interna manteve salvo.

Na segunda execução o contador estará com o valor 1, pois foi atualizado na primeira execução de funcaoInterna()

O código vai funcionar assim

// [Sem Closure]

// cria o escopo na memória
const foo = () => {
    let contador = 0;

    contador++;
}
// acabou o escopo
// O coletor de lixo vai limpar tudo que estava no escopo da memória


// [Com Closure]

// cria o escopo na memória
const foo = () => {
    let contador = 0;

    // cria o escopo na memória
    const bar = () => {
        contador++;
        console.log(contador);
    }

    return bar;
}
// acabou o escopo
// Como a função bar é retornada
// ela continua existindo na memória, mesmo que a função foo tenha acabado
// isso mantem o escopo da função foo na memória, mesmo que a função foo tenha acabado 
Enter fullscreen mode Exit fullscreen mode

Exemplos

Irei listar alguns exemplo de uso de closures no javascript.

Debounce

Um dos usos de closures é para criar um debounce. O Debounce atrasa a execução de uma função para que caso a função seja chamada novamente dentro desse intervalo, ela reinicia esse atraso para garantir que não execute a função novamente.

Isso é útil para caso um usuário clique várias vezes em um botão que faz uma chama a uma API. Garantindo que a chamada só ocorra quando o usuário para de clicar várias vezes.

const debounce = (func, wait = 100) => {
    let timeout;

    // closure recebe todos os argumentos
    return (...args) => {
        clearTimeout(timeout);

        timeout = setTimeout(() => {
            // passa todos os argumentos para a função
            func(...args);
        }, wait)
    }
}

const chamaAPI = async () => {
    const response = await fetch('https://jsonplaceholder.typicode.com/todos/1');
    const data = await response.json();
    console.log(data);
}

// espera 2 segundos para chamar a API.
const debouncedChamaAPI = debounce(chamaAPI, 2000);

// Simulando o clicks rápidos do usuário
debouncedChamaAPI();
debouncedChamaAPI();
debouncedChamaAPI();

Enter fullscreen mode Exit fullscreen mode

Cache

Outro uso de closure é para criar uma função com um cache para casos onde o processamento dos resultados é muito custoso ou lento.

const calcWithCache = () => {
    const cache = {}; // cria um cache para armazenar os resultados

    // closure que faz o cálculo e armazena no cache
    return (num) => {
        if (cache[num]) {
            console.log("Retornando do cache");
            return cache[num];
        }

        console.log("Calculando... Processamento pesado");
        const resultado = num * 999999; // Simulando um cálculo pesado

        cache[num] = resultado;
        return resultado;
    };
}

const calc = calcWithCache();

console.log(calc(5));
console.log(calc(10));
console.log(calc(5)); // Deve retornar do cache
Enter fullscreen mode Exit fullscreen mode

Top comments (0)