@rodbv

Arquivo para setembro 16th, 2010

Javascript pra gente grande: entendendo closures

com 20 comentários

Crock NorrisNo fim-de-semana passado estive na QCon, evento muito legal que contou com a presença do “guru” do Javascript, Douglas Crockford.

Ele deu uma geral no passado e futuro da linguagem Javascript Ecmascript, e um dos slides que mais me chamou dizia o seguinte (tradução minha):

Escrever código ECMAScript sem entender closures é como escrever código Java sem entender classes.

Mensagem mais clara, impossível. E eu concordo. Javascript é uma linguagem que quase ninguém realmente senta pra estudar: a maioria das pessoas simplesmente assume que ela é meio parecida com Java (Java+script, certo?), e assim vai, escrevendo funções e declarando variáveis.

Mas para quem realmente se dedica a estudar Javascript um pouco mais a fundo, descobre uma bela linguagem, altamente funcional (no sentido F# da coisa) e expressiva.

Pra podermos tirar vantagem dessa linguagem, é importante entendermos o tripé conceitual que forma o Javascript:

  • Funções
  • Objetos
  • Closures

Vamos então dar uma olhada nesses três elementos:

Funções

No Javascript, funções são cidadãos de primeira classe. Isso significa que você pode assinalar uma função a uma variável, passar uma função como parâmetro em outra função, ou retornar uma função a partir de outra função, sem complicações sintáticas.

//funções como variáveis
var fn = function(mensagem){
    alert(mensagem);
};
fn("Ola"); //"Olá"

//função como parâmetro
function chamaFuncao(funcao, texto){
    funcao(texto);
}
chamaFuncao(alert, "Ola de novo"); //Olá de novo

//funcao sendo retornada em outra funcao (gerador de funcoes)
function geraSomador(numero){
    return function(x) { return x + numero;};
}
var soma5 = geraSomador(5);
alert(soma5(4)); //"9"

Objetos

Objetos em Javascript são simplesmente uma coleção de chaves e valores. Tudo em Javascript são objetos: funções, arrays, expressões regulares etc.

O fato de objetos serem apenas uma coleção de chaves e valores fazem que a sintaxe de criação seja bem simples:

var pessoa = {
     nome : "Douglas Crockford",
     profissao : "Guru Javascript"
};
alert(pessoa.nome); //Douglas Crockford
alert(pessoa["profissao"]); //Guru Javascript

Note também que eu posso acessar um atributo de um método tanto usando a notação com ponto, quanto usando a notação com colchete. A notação da linha 6 permite que eu acesse tais atributos dinamicamente, o que pode ser muito útil. Podemos também adicionar novos atributos a um objeto, depois de ele ter sido criado:

pessoa.email = "crock@yahoo.com";
pessoa["telefone"] = "+1 746 4840";
alert(pessoa.email); //crock@yahoo.com
alert(pessoa.telefone); //+1 746 4840
alert(pessoa.umAtributoQueNaoExiste); //undefined

Como o valor de um atributo pode ser qualquer objeto, podemos colocar funções e outros objetos ali:

var pessoa = {
    nome: "Douglas Crockford",
    dizAlgumaCoisa: function(coisa){
        alert(coisa);
    },
    telefones: {
        casa: "+1 434 3301",
        trabalho: "+1 434 9785"
    }
}

pessoa.dizAlgumaCoisa("Pode me chamar de Crock"); //Pode me chamar de Crock
alert(pessoa.telefones.casa); //+1 434 3301

Vendo o código cima já dá pra sentir um jeitão de orientação a objetos que tanto gostamos em C# e Java; mas o que temos em mãos é um objeto, como criamos algo que funciona como uma classe? Fácil, basta criarmos uma função que retorna objetos, e usarmos a palavra-chave new:

function Pessoa(nomeDaPessoa){
    return {
        nome: nomeDaPessoa,
        dizAlgumaCoisa: function(coisa){
            alert(coisa);
        },
        telefones: {
            casa: "+1 434 3301",
            trabalho: "+1 434 9785"
        };
    }
}

var crock = new Pessoa("Douglas Crockford");
crock.dizAlgumaCoisa(crock.nome);  //Douglas Crockford
crock.dizAlgumaCoisa(crock.telefones.trabalho); //+1 434 9785

Tá ficando legal, mas quando pensamos em classes, uma das primeiras coisas que vêm à cabeça é a possibilidade de termos atributos privados, e é aí que closures vêm nos ajudar.

Closures

Closure em inglês significa encerramento, no sentido de guardar, pôr em um lugar fechado. Repare no código que usamos lá em cima:

//funcao sendo retornada em outra funcao (gerador de funcoes)
function geraSomador(numero){
    return function(x) { return x + numero;};
}
var soma5 = geraSomador(5);
alert(soma5(4)); //"9"

Uma coisa muito curiosa aconteceu aqui: a minha nova função, soma5, de alguma forma lembra (encerra) o valor do parâmetro numero, depois de criada. Então tudo que declaramos dentro de uma função é privado, mas pode ser usado por membros públicos.

Resumindo então: closures são blocos de código que podem ser executados a qualquer momento, guardando o contexto no qual eles foram criados, mesmo quando a função que criou esse bloco já saiu de escopo.

..e isso obviamente vai cair muito bem pra gente criar uma classe bem encapsulada!

function Pessoa(nomeDaPessoa){
    //membros privados
    var _livros = [];
    var _ordenaLivros = function(){
        return _livros.sort();
    }

    //membros publicos
    return {
        adicionaLivro: function(livro) {
            _livros.push(livro);
            //return this;
        },
        meusLivros: function(){
            return _ordenaLivros(_livros).join(", ");
        }
    };
}

var p = new Pessoa("Crock Norris");
p.adicionaLivro("Javascript: The Good Parts");
p.adicionaLivro("Das Beste an Javascript");
alert(p.meusLivros()); //Das Beste an Javascript, Javascript: The Good Parts

Repare que o método adicionaLivro tem um return this comentado; se a gente descomentar essa linha, a função vai retornar uma referência ao próprio objeto. Isso permite encadeamento de métodos, para uma interface mais fluente:

    alert(p.adicionaLivro("Javascript: the good parts")
        .adicionaLivro("Das Beste an Javascript")
        .meusLivros()); //Das Beste an Javascript, Javascript: The Good Parts

Um outro uso legal de closures é evitar a criação desnecessária de variáveis globais (uma coisa ruim em qualquer linguagem). Podemos usar uma função anônima que é executada imediatamente (linha 8 ) para criar um bloco de código com suas variáveis locais, que saem de escopo assim que a função termina (ao invés de ficarem penduradas na página pra sempre) e evita conflitos com outras variáveis e funções da página:

//....
cor = "blue";
//...

(function(){
    var cor = "#FF0000";
    document.body.style.background = cor; //deixa o fundo da pagina vermelha
})();
alert(cor); //blue

Bem, por enquanto é isso :) Pretendo escrever mais sobre aspectos legais do Javascript, aceito sugestões.

Escrito por rodbv

16/09/2010 em 00:59

Publicado em Javascript, Programação

Seguir

Obtenha todo post novo entregue na sua caixa de entrada.