Como funcionam Closures em JavaScript?

Como funcionam Closures em JavaScript?

27 de setembro de 2017 1 Por Ramos de Souza Janones
Como vender Software - Seja desktop, web ou MobilePowered by Rock Convert
Powered by Rock Convert

Entenda como funcionam Closures em JavaScript, com exemplos práticos para uma melhor qualidade de desenvolvimento de aplicações.

Closure (“clausura” em português, mas esse termo é raramente utilizado), se refere à forma como funções definidas dentro de um “contexto léxico” (i.e. o corpo de uma função, um bloco, um arquivo fonte) acessam variáveis definidas nesse contexto.

Em JavaScript, apenas funções definem um novo contexto léxico (outras linguagens têm regras diferentes – algumas sequer suportam o conceito de closure):

var a = 10; // Mesmo "a" para script1.js, script2.js, etc (efetivamente, uma global)
function f() {
    var b = 20; // Um "b" diferente para cada invocação de f
    if ( x ) {
        var c = 30; // Mesmo "c" dentro e fora do if (i.e. o contexto é "f", não o bloco if)

E cada novo contexto criado dentro (inner) de um contexto já existente tem acesso a todas as variáveis definidas no “de fora” (outer):

 

function x(a1) {          // "x" tem acesso a "a"
    var a2;
    function y(b1) {      // "y" tem acesso a "a" e "b"
        var b2;
        function z(c1) {  // "z" tem acesso a "a", "b", e "c"
            var c2;

 

É importante observar que não importa quando a função interna irá executar, nem qual o valor as variáveis externas tinham no momento em que o objeto função foi criado (em contraste com a definição da função, que é em tempo de compilação/interpretação). O que importa é que ambas compartilham a mesma variável, e escritas de um lado refletirão nas leituras do outro e vice-versa.

Pitfalls

Um exemplo de erro comum envolvendo closures é a criação de uma função dentro de um bloco for:

for ( var i = 0 ; i < elementos.length ; i++ ) {
    elementos[i].onclick = function() {
        alert("Esse é o elemento " + i);
    }
}

Esse código não funciona como esperado, uma vez que a variável i utilizada pela função anônima é o mesmo i do contexto externo – o que significa que quando o i externo muda, o valor que a função interna vai acessar é diferente. No final, i será igual a elementos.length (ex.: 10), de modo que clicar em qualquer elemento sempre imprimirá “Esse é o elemento 10”.

Uma possível solução para esse problema é a criação de um novo contexto léxico que “capture” o valor daquela variável no momento desejado:

Powered by Rock Convert
for ( var i = 0 ; i < elementos.length ; i++ )
    (function(i) {
        elementos[i].onclick = function() {
            alert("Esse é o elemento " + i);
        }
    })(i);

 

LEIA TAMBÉM:  Como desenvolver aplicações AR/VR com Javascript e React

Dessa forma, o i parâmetro da função não é o mesmo i usado pelo laço for – e ele possui o valor que a variável tinha no momento da execução.

Utilidade

Existem muitas vantagens em se usar closures, como exemplificado na resposta do @Jordão (que demonstra um meio de se implementar currying). Um outro exemplo seria simular variáveis privadas – algo que não é normalmente suportado pela linguagem JavaScript:

function MeuObjeto() {
    this.publico = { ... }
    var privado = { ... }
    this.foo = function() {
        return privado.a;
    }
    this.bar = function(x) {
        privado.a = x;
    }
    ...
}
var obj = new MeuObjeto();
obj.publico; // Acessível
obj.privado; // undefined

 

LEIA TAMBÉM:  Quando é interessante desnormalizar o banco de dados?

Note que, como não existe nenhuma referência direta para privado, esse objeto não pode ser manipulado diretamente (apenas indiretamente por meio de foo e bar). Mas como foo e barforam criados dentro do contexto léxico do construtor, elas têm acesso às variáveis locals do mesmo, podendo acessá-las normalmente.

Um outro exemplo “clássico” é o Accumulator Generator, citado num artigo do Paul Graham (em inglês) onde se discute o poder de expressividade relativa das diversas linguagens de programação. O requisito é simples:

Escreva uma função foo que recebe um número n e retorna uma função que recebe um número i, e retorna n incrementado de i.

Nota: (a) [o argumento] é um número, não um inteiro. (b) é incrementado de, não mais.

A solução proposta, com exemplos de uso:

function foo (n) { 
    return function (i) { 
        return n += i;
    } 
}

var x = foo(10);
x(2); // 12
x(3); // 15

var y = foo(20);
y(5); // 25
y(2); // 27

 

Como os exemplos no final do artigo mostram, linguagens que não suportam closures acabam sendo muito mais varbosas (exigindo muito código para se fazer pouco), de modo que demoram mais para serem escritas, lidas, podem conter mais bugs (já que a probabilidade de bugs aumenta com a quantidade de linha de código), etc.

» Programação » Javascript 

Powered by Rock Convert
Siga os bons!
Últimos posts por Ramos de Souza Janones (exibir todos)
vote
Article Rating
LEIA TAMBÉM:  Mas, afinal, como é gerada a randomização pelo computador?