Arquivo para setembro 16th, 2010
Javascript pra gente grande: entendendo closures
No 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.