Los closures son un patrón de programación en JavaScript que permite usar variables de un ámbito léxico externo dentro de un bloque de código anidado. JavaScript admite los closures de forma transparente y a menudo se usan sin saber qué son.
// Top-level declarations are global-scope
const dozen = 12;
{
// Braces create a new block-scope
// Referencing the outer variable is a closure.
const twoDozen = dozen * 2;
}
// Because of the block-scope declaration, twoDozen is not available here.
twoDozen;
// => Uncaught ReferenceError: twoDozen is not defined
Además de las llaves {}, las funciones (y las clases) también crean nuevos ámbitos y pueden encerrar valores:
const dozen = 12;
// Functions create a new function-scope and block-scope.
// Referencing the outer variable here is a closure.
function nDozen(n) {
// This is declared inside the function scope, and uses the top-level scope.
// This works, and encloses the value 12.
const twoDozen = dozen * 2;
// This only uses the locally declared variable and the passed argument to the parameter `n`
return (twoDozen / 2) * n;
}
// Because of the function-scope declaration, twoDozen is not available here.
twoDozen;
// => Uncaught ReferenceError: twoDozen is not defined
Como muestran los ejemplos de twoDozen, los valores se pueden encerrar en un ámbito anidado (función, bloque, etc.), pero no se pueden extraer de ese contexto.
En la mayoría de los casos, en el JavaScript moderno se pretende que un valor no se filtre a un ámbito externo.
Usar una declaración de variable mutable (como let o var) permite conservar algo de estado:
let counter = 0;
// This function closure increments the counter's state in the outer lexical context.
// This way the counter can be shared between many calling contexts.
export function increment() {
counter += 1;
return counter;
}
increment();
// => 1
counter;
// => 1
Si combinamos las dos ideas de encerrar un valor para conservar el estado y de que los valores encerrados no se filtren al exterior, es posible crear valores privados.
El método más común es crear una función que devuelva una función que encierre algún estado.
export function makeCounter() {
let counter = 0;
// This returns a new function that encloses the local variable counter
return function increment() {
counter += 1;
return counter;
};
}
// Counter did not leak
counter;
// => Uncaught ReferenceError: counter is not defined
// This creates a new counter.
// This assigns the increment function to the variable myFirstCounter.
const myFirstCounter = makeCounter();
typeof myFirstCounter;
// => function
myFirstCounter.name;
// => increment
myFirstCounter();
// => 1
myFirstCounter();
// => 2
// This creates new counter (with new, separate local state / enclosed counter variable)
const mySecondCounter = makeCounter();
mySecondCounter();
// => 1
// It does not affect the first counter.
myFirstCounter();
// => 3
A muchos programadores les parece que los closures son un concepto difícil, y devolver una función desde una función no es habitual o ni siquiera es posible en todos los lenguajes de programación. Si quieres más material de lectura, la guía sobre closures de MDN es bastante completa.
Tu empresa de diseño ha estado trabajando principalmente con transformaciones CSS para crear páginas web. Tras algunas conversaciones, se decide empezar a usar JavaScript para realizar algunos cálculos de forma dinámica. Algunos de tus compañeros de equipo tienen menos experiencia con JavaScript, así que decides usar un cierre de función para crear transformaciones reutilizables para pares de coordenadas {x, y}.
Implementa la función translate2d que devuelve una función que hace uso de un cierre para realizar una traslación 2d repetible de un par de coordenadas.
En geometría, la traslación se refiere a mover puntos, vectores o formas la misma distancia en una dirección. Puede interpretarse como la suma de una constante a cada punto.
const moveCoordinatesRight2Px = translate2d(2, 0);
const result = moveCoordinatesRight2Px(4, 8);
// result => [6, 8]
Implementa la función scale2d que devuelve una función que hace uso de un cierre para realizar un escalado 2d repetible de un par de coordenadas.
En geometría, el escalado uniforme se refiere a agrandar o reducir vectores o formas en la misma dirección. Puede interpretarse como multiplicar cada punto por una constante (factor de escala).
Para este ejercicio, asume solo valores de escalado positivos.
const doubleScale = scale2d(2, 2);
const result = doubleScale(6, -3);
// result => [12, -6]
Combina dos funciones de transformación para realizar una transformación repetible. A esto se le suele llamar composición de funciones, donde el resultado de la primera función 'f(x)' se usa como entrada de la segunda función 'g(x)'.
const moveCoordinatesRight2Px = translate2d(2, 0);
const doubleCoordinates = scale2d(2, 2);
const composedTransformations = composeTransform(
moveCoordinatesRight2Px,
doubleCoordinates,
);
const result = composedTransformations(0, 1);
// result => [4, 2]
Implementa la función memoizeTransform. Recibe una función para memoizar y devuelve una nueva función que recuerda las entradas de la función recibida, de modo que el último valor devuelto pueda «recordarse» y calcularse solo una vez si se vuelve a llamar con los mismos argumentos.
La memoización se usa a veces en la programación dinámica. Permite que las operaciones costosas se realicen solo una vez, ya que sus resultados se recuerdan. Nota: en este ejercicio solo se recuerda el último resultado, a diferencia de algunas soluciones de programación dinámica que memorizan todos los resultados.
const tripleScale = scale2d(3, 3);
const memoizedScale = memoizeTransform(tripleScale);
memoizedScale(4, 3); // => [12, 9], this is computed since it hasn't been computed before for the arguments
memoizedScale(4, 3); // => [12, 9], this is remembered, since it was computed already
Regístrate en Exercism para aprender y dominar JavaScript con 37 conceptos159 ejercicios y mentoría humana real, todo gratis.