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"
- O tipo de retorno de
outerFunction
é() => void
, indicando que retorna uma função. - O tipo de
innerFunction
é explicitamente definido comovoid
, indicando que não possui valor de retorno.
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 do tipo() => number
. - 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. 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
recebe um argumento do tiponumber
e retorna uma função do tipo(value: number) => number
.- A função interna também aceita
value
como tiponumber
e retorna o resultado como tiponumber
.
Exemplo de Implementação de Closures em TypeScript
Implemente um contador com limite de intervalo como uma closure com anotações de tipo adicionais.
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 demonstra um caso onde a variável i
não funciona 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.