Closure em TypeScript

Closure em TypeScript

Neste artigo, vamos explicar closures em TypeScript.

YouTube Video

Closure em TypeScript

O que é uma Closure?

Closure refere-se à capacidade de manter referências ao escopo (ambiente) em que uma função foi definida, mesmo que a função seja chamada fora desse escopo. A seguir, closures serão explicados incluindo anotações de tipo.

Simplificando, uma closure combina uma função e o ambiente das variáveis onde a função foi definida, permitindo o acesso a esse ambiente quando a função é chamada.

Mecanismo Básico de Closures

Em TypeScript, quando uma função é definida dentro de outra função, fica evidente que a função interna pode acessar as variáveis da função externa. Aqui está um exemplo básico de closure com anotações de tipo.

 1function outerFunction(): () => void {
 2    let outerVariable: string = "I am from outer function";
 3
 4    function innerFunction(): void {
 5        // The inner function accesses the variable of the outer function
 6        console.log(outerVariable);
 7    }
 8
 9    return innerFunction;
10}
11
12const closure: () => void = outerFunction();
13closure();  // "I am from outer function"
  • outerFunction retorna a função interna innerFunction.
  • innerFunction exibe o valor da variável outerVariable da função externa.

Usos e Vantagens das Closures

Encapsulamento de Dados

Abaixo está um exemplo de closure com anotações de tipo para encapsulamento de dados.

 1function createCounter(): () => number {
 2    let count: number = 0;
 3
 4    return function (): number {
 5        count += 1;
 6        return count;
 7    };
 8}
 9
10const counter: () => number = createCounter();
11console.log(counter());  // 1
12console.log(counter());  // 2
13console.log(counter());  // 3
  • A função createCounter retorna uma função que retorna um valor do tipo number.
  • A variável count é definida como tipo number e é manipulada dentro da closure.

Funções de Alta Ordem

Closures são úteis ao criar funções de alta ordem. Aqui, uma função de ordem superior é uma função que recebe outra função como argumento ou retorna uma função como resultado. Abaixo está um exemplo de uma função de alta ordem com anotações de tipo claras.

 1function createMultiplier(multiplier: number): (value: number) => number {
 2    return function (value: number): number {
 3        return value * multiplier;
 4    };
 5}
 6
 7const double: (value: number) => number = createMultiplier(2);
 8console.log(double(5));  // 10
 9
10const triple: (value: number) => number = createMultiplier(3);
11console.log(triple(5));  // 15
  • createMultiplier é uma função de ordem superior que cria uma função para multiplicar pelo número que recebe como argumento.
  • A função interna de multiplicação também recebe um número e retorna o resultado da multiplicação como um número.

Exemplo de Implementação de Closures em TypeScript

Vamos também ver um exemplo de implementação de um contador baseado em intervalo como um fechamento.

 1function rangeCounter(min: number, max: number): () => number | string {
 2    let count: number = min;
 3
 4    return function (): number | string {
 5        if (count <= max) {
 6            return count++;
 7        } else {
 8            return `Count has exceeded the maximum value: ${max}`;
 9        }
10    };
11}
12
13const counter: () => number | string = rangeCounter(1, 5);
14
15console.log(counter());  // 1
16console.log(counter());  // 2
17console.log(counter());  // 3
18console.log(counter());  // 4
19console.log(counter());  // 5
20console.log(counter());  // "Count has exceeded the maximum value: 5"
  • A função rangeCounter retorna uma função que retorna um number ou uma string.
  • Na função interna, se count exceder max, retorna uma mensagem do tipo string; caso contrário, retorna um valor do tipo number.

Precauções ao Usar Closures

Possível Vazamento de Memória por Closures

Closures podem manter variáveis de um escopo externo, o que às vezes pode causar vazamento de memória. Closures desnecessárias precisam ser explicitamente liberadas da memória.

 1function createLeak(): () => void {
 2    // Large array consuming significant memory
 3    const largeArray: string[] = new Array(1000000).fill("leak");
 4
 5    // Closure capturing `largeArray`
 6    return function (): void {
 7        console.log(largeArray[0]); // Using the captured array
 8    };
 9}
10
11// Create a closure that holds a reference to the large array
12let leakyFunction = createLeak();
13
14// The large array is not released as `leakyFunction` still references it
15
16// When the object is no longer needed
17leakyFunction = null; // Explicitly remove the reference
  • Neste código, o largeArray criado dentro de createLeak deveria ser liberado quando sai do escopo, mas não é porque a closure captura largeArray. Enquanto leakyFunction existir, essa memória desnecessária continuará sendo retida.
  • Quando um objeto ou variável não é mais necessário, definir sua referência como null permite que o coletor de lixo a detecte e libere a memória.

Mau Uso de Closures em Laços

Ao criar closures dentro de um laço, podem ocorrer problemas ao referenciar a mesma variável. O exemplo a seguir mostra um caso em que a variável i declarada com var não se comporta corretamente.

1for (var i: number = 0; i < 3; i++) {
2    setTimeout((): void => {
3        console.log(i);
4    }, 1000);
5}
6// Output: 3, 3, 3

Este código não produz o resultado desejado porque i refere-se ao valor 3 no final do laço. Para corrigir isso, use let para separar o escopo ou utilize uma função auto-invocada.

1for (let i: number = 0; i < 3; i++) {
2    setTimeout((): void => {
3        console.log(i);
4    }, 1000);
5}
6// Output: 0, 1, 2

Ao usar let, o escopo de i é separado para cada iteração do laço, gerando os resultados esperados.

Resumo

Em TypeScript, closures podem resultar em código mais seguro e previsível aproveitando o sistema de tipos. O uso adequado de closures permite o encapsulamento de dados e um design flexível de funções de alta ordem. Além disso, é preciso ter cuidado com o gerenciamento de memória e referências de escopo indesejadas ao usar closures.

Você pode acompanhar o artigo acima usando o Visual Studio Code em nosso canal do YouTube. Por favor, confira também o canal do YouTube.

YouTube Video