Clausura en TypeScript
En este artículo, explicaremos las clausuras en TypeScript.
YouTube Video
Clausura en TypeScript
¿Qué es una clausura?
Clausura se refiere a la capacidad de retener referencias al ámbito (entorno) en el que una función fue definida, incluso si la función es llamada fuera de ese ámbito. A continuación, las clausuras serán explicadas incluyendo anotaciones de tipo.
En pocas palabras, una clausura combina una función y el entorno de variables donde la función fue definida, permitiendo el acceso a ese entorno cuando la función es llamada.
Mecanismo básico de las clausuras
En TypeScript, cuando una función se define dentro de otra función, se hace evidente que la función interna puede acceder a las variables de la función externa. Aquí tienes un ejemplo básico de una clausura con anotaciones 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"
- El tipo de retorno de
outerFunction
es() => void
, indicando que devuelve una función. - El tipo de
innerFunction
se establece explícitamente envoid
, indicando que no tiene valor de retorno.
Usos y ventajas de las clausuras
Encapsulamiento de datos
A continuación se muestra un ejemplo de una clausura con anotaciones de tipo para encapsular datos.
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
- La función
createCounter
devuelve una función de tipo() => number
. - La variable
count
se define como de tiponumber
y se manipula dentro de la clausura.
Funciones de orden superior
Las clausuras son útiles al crear funciones de orden superior. A continuación se muestra un ejemplo de una función de orden superior con anotaciones 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
toma un argumento de tiponumber
y devuelve una función de tipo(value: number) => number
.- La función interna también acepta
value
como de tiponumber
y devuelve el resultado como tiponumber
.
Ejemplo de implementación de clausuras en TypeScript
Implementa un contador limitado por rango como una clausura con anotaciones de tipo añadidas.
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"
- La función
rangeCounter
devuelve una función que retorna unnumber
o unstring
. - En la función interna, si
count
superamax
, retorna un mensaje de tipostring
; de lo contrario, retorna un tiponumber
.
Precauciones al usar clausuras
Posibles pérdidas de memoria por clausuras
Las clausuras pueden retener variables de un ámbito externo, lo que a veces puede causar pérdidas de memoria. Las clausuras innecesarias deben ser liberadas explícitamente de la memoria.
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
- En este código, el
largeArray
creado dentro decreateLeak
debería liberarse cuando sale del ámbito, pero no ocurre porque la clausura capturalargeArray
. MientrasleakyFunction
exista, esta memoria innecesaria seguirá siendo retenida. - Cuando un objeto o variable ya no es necesario, establecer su referencia a
null
permite que el recolector de basura lo detecte y libere la memoria.
Uso indebido de las clausuras en bucles
Al crear clausuras dentro de un bucle, pueden surgir problemas al referirse a la misma variable. El siguiente ejemplo muestra un caso en el que la variable i
no funciona correctamente.
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 no arroja el resultado deseado porque i
se refiere al valor 3 al final del bucle. Para solucionar esto, utiliza let
para separar el ámbito o emplea una función invocada inmediatamente.
1for (let i: number = 0; i < 3; i++) {
2 setTimeout((): void => {
3 console.log(i);
4 }, 1000);
5}
6// Output: 0, 1, 2
Al emplear let
, el ámbito de i
se separa para cada iteración del bucle, lo que da los resultados esperados.
Resumen
En TypeScript, las clausuras pueden llevar a un código más seguro y predecible al aprovechar el sistema de tipos. El uso adecuado de las clausuras permite el encapsulamiento de datos y un diseño flexible de funciones de orden superior. Además, se debe tener cuidado con la gestión de la memoria y las referencias de ámbito no deseadas al usar clausuras.
Puedes seguir el artículo anterior utilizando Visual Studio Code en nuestro canal de YouTube. Por favor, también revisa nuestro canal de YouTube.