Closures são um padrão de programação em JavaScript que permite usar variáveis de um escopo léxico externo dentro de um bloco de código aninhado. O JavaScript dá suporte a closures de forma transparente, e muitas vezes elas são usadas sem que se saiba 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 chaves {}, as funções (e as classes) também criam novos escopos e podem 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 mostram os exemplos com twoDozen, os valores podem ficar encerrados em um escopo aninhado (função, bloco etc.), mas não podem ser retirados desse contexto.
Na maioria dos casos, a intenção no JavaScript moderno é que um valor não vaze para um escopo externo.
Usar uma declaração de variável mutável (como let ou var) permite preservar certo 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
Combinando as duas ideias: encerrar um valor para preservar estado, e o fato de que valores encerrados não vazam para fora, é possível criar valores privados.
O método mais comum é criar uma função que retorna uma função que encerra certo 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
Muitos programadores acham closures um conceito difícil, e retornar uma função de dentro de uma função não é comum, nem mesmo possível em todas as linguagens de programação. Se você quiser mais material de leitura, o guia do MDN sobre Closures é bem completo.
Sua empresa de design tem trabalhado principalmente com transformações CSS para construir páginas web. Depois de algumas discussões, foi decidido começar a usar JavaScript para fazer alguns cálculos dinamicamente. Alguns dos seus colegas de equipe têm menos experiência com JavaScript, então você decide usar um closure para criar uma transformação reutilizável para pares de coordenadas {x, y}.
Implemente a função translate2d que retorna uma função que usa um closure para realizar uma translação 2d repetível de um par de coordenadas.
Em geometria, translação se refere a mover pontos, vetores ou formas a mesma distância em uma mesma 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]
Implemente a função scale2d que retorna uma função que usa um closure para realizar uma escala 2d repetível de um par de coordenadas.
Em geometria, escala uniforme se refere a aumentar ou diminuir vetores ou formas na mesma direção. Pode ser interpretada como a multiplicação de cada ponto por uma constante (fator de escala).
Para este exercício, considere apenas valores de escala positivos.
const doubleScale = scale2d(2, 2);
const result = doubleScale(6, -3);
// result => [12, -6]
Combine duas funções de transformação para realizar uma transformação repetível. Isso costuma ser chamado de composição de funções, em que o resultado da primeira função 'f(x)' é usado como entrada para a 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]
Implemente a função memoizeTransform. Ela recebe uma função para memoizar e então retorna uma nova função que lembra as entradas da função fornecida, de modo que o último valor de retorno possa ser "lembrado" e calculado apenas uma vez se a função for chamada novamente com os mesmos argumentos.
A memoização às vezes é usada em programação dinâmica. Ela permite que operações custosas sejam feitas apenas uma vez, já que seus resultados ficam guardados. Observe que neste exercício apenas o último resultado é lembrado, diferente de algumas soluções em programação dinâmica que memoizam 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
Crie sua conta no Exercism para aprender e dominar JavaScript com 37 conceitos159 exercícios e mentoria humana de verdade, tudo de graça.