Tracks
/
JavaScript
JavaScript
/
Ejercicios
/
Transformación de coordenadas
Transformación de coordenadas

Transformación de coordenadas

Ejercicio de aprendizaje

Introducción

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 muchas veces 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 ámbitos nuevos, 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 sacar de ese contexto. En la mayoría de los casos, en el JavaScript moderno se busca que un valor no se filtre hacia un ámbito externo.

Closures para conservar el estado y pasar valores

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

Encerrar valores sin filtrar el estado

Si combinamos las dos ideas, encerrar un valor para conservar el estado y que los valores encerrados no se filtren hacia fuera, es posible crear valores privados.

El método más común es crear una función que devuelve una función que encierra algo de 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
Note

A muchos programadores les parece que los closures son un concepto difícil, y devolver una función desde otra función no es común, y 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.

Instrucciones

Tu empresa de diseño ha estado trabajando principalmente con transformaciones CSS para crear páginas web. Después de algunas conversaciones, se decide empezar a usar JavaScript para realizar algunos cálculos de forma dinámica. Algunos de tus compañeros tienen menos experiencia con JavaScript, así que decides usar un closure para crear transformaciones reutilizables de pares de coordenadas {x, y}.

1. Traslada las coordenadas

Implementa la función translate2d, que devuelve una función que usa un closure 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 figuras la misma distancia en una misma dirección. Se puede interpretar como la suma de una constante a cada punto.

const moveCoordinatesRight2Px = translate2d(2, 0);
const result = moveCoordinatesRight2Px(4, 8);
// result => [6, 8]

2. Escala las coordenadas

Implementa la función scale2d, que devuelve una función que usa un closure 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 figuras en la misma dirección. Se puede interpretar como multiplicar cada punto por una constante (el factor de escala).

Para este ejercicio, supón que solo hay valores de escala positivos.

const doubleScale = scale2d(2, 2);
const result = doubleScale(6, -3);
// result => [12, -6]

3. Compón funciones de transformación

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 argumento 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]

4. Guarda los resultados de las funciones

Implementa la función memoizeTransform. Recibe una función para memoizar y luego devuelve una nueva función que recuerda los argumentos de la función suministrada, de modo que el último valor devuelto se pueda «recordar» y solo se calcule una vez si se la 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 hagan una sola 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 memoizan 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
Editar en GitHub El enlace se abre en una ventana o una pestaña nuevas
JavaScript Exercism

¿Todo listo para empezar Transformación de coordenadas?

Regístrate en Exercism para aprender y dominar JavaScript con 37 conceptos159 ejercicios y mentoría humana real, todo gratis.