Clausura en TypeScript

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 en void, 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 tipo number 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 tipo number y devuelve una función de tipo (value: number) => number.
  • La función interna también acepta value como de tipo number y devuelve el resultado como tipo number.

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 un number o un string.
  • En la función interna, si count supera max, retorna un mensaje de tipo string; de lo contrario, retorna un tipo number.

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 de createLeak debería liberarse cuando sale del ámbito, pero no ocurre porque la clausura captura largeArray. Mientras leakyFunction 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.

YouTube Video