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 internainnerFunction
.innerFunction
exibe o valor da variávelouterVariable
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 tiponumber
. - A variável
count
é definida como tiponumber
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 umnumber
ou umastring
. - Na função interna, se
count
excedermax
, retorna uma mensagem do tipostring
; caso contrário, retorna um valor do tiponumber
.
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 decreateLeak
deveria ser liberado quando sai do escopo, mas não é porque a closure capturalargeArray
. EnquantoleakyFunction
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.