Percursos
/
JavaScript
JavaScript
/
Exercícios
/
Transformação de coordenadas
Transformação de coordenadas

Transformação de coordenadas

Exercício de aprendizagem

Introdução

Closures são um padrão de programação em JavaScript que permite usar variáveis de um âmbito lexical exterior dentro de um bloco de código aninhado. O JavaScript suporta closures de forma transparente, e muitas vezes são usadas sem se saber o que são.

// 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

Além das chavetas {}, as funções (e as classes) também criam novos âmbitos e podem capturar 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 mostram os exemplos com twoDozen, os valores podem ser capturados num âmbito aninhado (função, bloco, etc.), mas não podem ser retirados desse contexto. Na maioria dos casos, o JavaScript moderno pretende que um valor não escape para um âmbito exterior.

Closures para guardar estado e passar valores

Usar uma declaração de variável mutável (como let ou var) permite preservar algum 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

Capturar valores sem deixar escapar o estado

Combinando as duas ideias, capturar um valor para preservar o estado e não deixar que os valores capturados escapem para fora, é possível criar valores privados.

O método mais comum é criar uma função que devolve uma função que captura algum 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

Muitos programadores consideram os closures um conceito difícil, e devolver uma função a partir de outra função não é comum, nem sequer é possível em todas as linguagens de programação. Se quiseres mais material de leitura, o guia da MDN sobre closures é bastante completo.

Instruções

A tua empresa de design tem trabalhado sobretudo com transformações CSS para construir páginas web. Após alguma discussão, decide-se começar a usar JavaScript para fazer alguns cálculos de forma dinâmica. Alguns dos teus colegas de equipa têm menos experiência com JavaScript, por isso decides usar uma closure de função para criar uma transformação reutilizável para pares de coordenadas {x, y}.

1. Translada as coordenadas

Implementa a função translate2d, que devolve uma função que usa uma closure para efetuar uma translação 2d repetível de um par de coordenadas.

Em geometria, a translação refere-se a mover pontos, vetores ou formas a mesma distância numa direção. Pode ser interpretada como a adição de uma constante a cada ponto.

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

2. Escala as coordenadas

Implementa a função scale2d, que devolve uma função que usa uma closure para efetuar uma escala 2d repetível de um par de coordenadas.

Em geometria, a escala uniforme refere-se a ampliar ou reduzir vetores ou formas na mesma direção. Pode ser interpretada como a multiplicação de cada ponto por uma constante (fator de escala).

Neste exercício, considera apenas valores de escala positivos.

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

3. Compõe funções de transformação

Combina duas funções de transformação para efetuar uma transformação repetível. A isto chama-se muitas vezes composição de funções, em que o resultado da primeira função 'f(x)' é usado como valor de entrada da segunda função '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 os resultados das funções

Implementa a função memoizeTransform. Recebe uma função para memorizar e devolve uma nova função que se lembra dos parâmetros de entrada da função fornecida, para que o último valor devolvido possa ser «lembrado» e calculado apenas uma vez se for chamada de novo com os mesmos argumentos.

A memorização é por vezes usada em programação dinâmica. Permite que operações dispendiosas sejam feitas apenas uma vez, uma vez que os seus resultados ficam guardados. Nota que, neste exercício, só o último resultado é guardado, ao contrário de algumas soluções de programação dinâmica que memorizam todos os 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 via GitHub A ligação abre numa nova janela ou separador
JavaScript Exercism

Estás pronto para começar Transformação de coordenadas?

Inscreve-te no Exercism para aprenderes e dominares JavaScript com 37 conceitos159 exercícios, e mentoria humana real, tudo grátis.