Arquivo para setembro 2010
Trabalha com JS? Então porque não aprende? :) (parte 2 – funções)

No post de ontem começamos a dar uma olhada geral na linguagem JavaScript, mas resolvi deixar para um post separado a parte central da linguagem, que são as funções.
A grande ‘sacada’ de quem começa a estudar JavaScript mais a fundo é entender que a linguagem é funcional, ou seja, funções são cidadãos de primeira-classe, podendo ser tratadas como qualquer outro objeto, passados como parâmetro, podem ser o resultado de outra função, etc.
Provavelmente a segunda grande sacada é o uso de funções anônimas. Quem usa jQuery com certeza vê muito código do tipo
$('#botao1').click(function() {
alert('Você clicou no botão 1.');
});
Repare que o parâmetro passado para a função click é uma função sem nome; isso é uma função anônima. Todas bibliotecas modernas de JavaScript usam e abusam de funções anônimas, que são especialmente úteis para programação assíncrona (podemos passar uma função anônima como parâmetro de callback).
Podemos também atribuir uma funcão anônima a uma variável
var minhaFuncao = function(nome) {
return "Olaaaa " + nome;
}
minhaFuncao("Enfermeira!"); //Olaaaa Enfermeira!
//A funcao anonima pode receber um nome
var minhaFuncao2 = function mf(nome) {
return "Bom dia " + nome;
}
minhaFuncao2("camarada"); //Bom dia camarada
Como vimos acima, a função anônima pode também receber um nome (por mais estranho que isso soe). Isso pode não parecer muito útil a princípio, mas nos ajuda quando lidamos com funções recursivas (afinal, como a função poderia chamar a ela mesma, se ela não tem nome?)
var fatorial = function fat(x) {
return x == 1 ? 1 : x * fat(x-1);
}
Antes que eu me esqueça, também podemos declarar funções no estilo tradicional:
function minhaFuncao(nome) {
return "Olaaaa " + nome;
}
minhaFuncao("Enfermeira!"); //Olaaaa Enfermeira!
..e podemos declarar funções dentro de funções:
function grita (nome) {
function maiusculo(txt) {
return txt.toUpperCase();
}
return "OLA " + maiusculo(nome) + " ADORO CAPS LOCK!!!";
}
alert(grita("amigao")); //OLA AMIGAO ADORO CAPS LOCK!
Naturalmente que a função maiusculo será acessível apenas dentro da função grita. Mas uma coisa interessante é que, dentro de um bloco de escopo, a função pode ser declarada em qualquer lugar, então se eu fizer o seguinte também funciona
function grita (nome) {
return "OLA " + maiusculo(nome) + " ADORO CAPS LOCK!!!";
function maiusculo(txt) {
return txt.toUpperCase();
}
}
alert(grita("amigao")); //OLA AMIGAO ADORO CAPS LOCK!
Como vimos no post sobre closures, esse padrão de funções-dentro-de-funções nos permite criar pseudo-classes, com métodos privados.
Mais uma curiosidade sobre funções em JavaScript: como funções são objetos, elas podem ter atributos. Isso nos permite implementar memoização de forma bem simples. Funções memoizadas são funções que lembram valores previamente computados, num cache (memo) local. Se por exemplo eu tenho uma função que calcula primos, eu posso evitar de recalcular primos que já foram calculados:
function primo(x){
//se a funcao ainda nao tem o memo, cria
if (!primo.memo)
primo.memo = [];
//checa se o memo ja possui o valor computado,
//se sim, retorna esse valor
if (primo.memo[x] != undefined)
return primo.memo[x];
//nao temos o valor computado, bora computar entao...
var ehPrimo = true;
for(var i = 2; i < Math.sqrt(x); i++) {
if (x % i == 0) {
ehPrimo = false;
break;
}
}
//guarda o valor no memo...
primo.memo[x] = ehPrimo;
// ...e retorna
return ehPrimo;
}
Argumentos
Funções JavaScript podem ser chamadas com qualquer número de argumentos, independente de quantos foram realmente especificados na declaração da função. Argumentos não utilizados ficam com o valor undefined.
Podemos também criar funções que aceitam um número qualquer de argumentos (funções variádicas), igual ao que se obtem com o uso de params[] em C#. Para isso basta acessarmos a variável implícita arguments, disponível em qualquer função:
function escreve(){
var ret = "";
for (var i = 0; i < arguments.length; i++)
ret += arguments[i] +";"
return ret;
}
alert(escreve(1,2,3)) //1;2;3
A variável arguments pode ser acessada por índice (arguments[0]) e pode ter o seu comprimento checado (arguments.length), mas não é um array verdadeiro, e sim um objeto do tipo Arguments. Lembre-se disso caso você veja algum erro inesperado na hora de manipular arguments (um dia isso vai acontecer, pode ter certeza). Douglas Crockford, um dos membros do comitê da linguagem JavaScript, afirmou numa palestra que isso foi um vacilo na criação da linguagem JavaScript, e que pretendem consertar no futuro.
Lembra que eu disse que precisávamos dar um nome a uma função anônima para poder torná-la recursiva? Na verdade, dá pra fazer isso também usando um outro atributo de arguments, que é arguments.callee. Então eu poderia fazer algo do tipo return x * arguments.callee(x-1); mas confesso que eu nunca achei outro uso para esse atributo.
Outra dica interessante é a seguinte: quando você tiver uma função com muitos parâmetros, é melhor beneficiar-se do fato de ser muito fácil criar objetos literais em JavaScript. Compare os dois métodos abaixo, que copia parte de um array para dentro de outro, pra ver qual é mais legível:
var a = [1,2,3,4,5,6,7,8,9];
var b = [100, 200, 300, 400]
//versao 1: com varios parametros
function copyArray(from, from_start, from_end, to, to_start) {
// ....
}
copyArray(a, 0, 3, b, 0);
//versao 2: com um objeto contendo parametros
function copyArray2(args) {
var from = args.from,
from_start = args.from_start || 0;
//....
}
copyArray2({from: a, to: b, from_end:3});
Contexto
Outro parâmetro implícito em toda função é o parâmetro this. Esse parâmetro, quando mal entendido, pode causar uns bugs bem chatinhos, mas a boa notícia é que não é difícil entender como ele funciona. Por outro lado, quando usados de forma inteligente ele permite tornar funções ainda mais flexíveis.
O parâmetro this é uma referência ao objeto a partir do qual a função está sendo chamada.
Moleza né? Ótimo. Por exemplo, se eu fizer o seguinte código
<input type="button" id="botao1" onclick="alert(this.id)" value="click me"/>
..quando eu apertar o botão, eu serei saudado com o id do mesmo. Sem mistério.
Isso fica legal quando eu uso uma biblioteca tipo jQuery, que me permite associar uma função a vários controles de uma vez:
<input type="button" id="botao1" value="click me"/>
<input type="button" id="botao2" value="click me"/>
<input type="button" id="botao3" value="click me"/>
...
<script>
$(function(){
$("input:button").click(function() {
alert(this.id);
});
})
</script>
Cada botão que eu apertar vai me mostrar o seu id. Agora, se uma função é rodada sem ser parte de um objeto, this vai se referenciar ao objeto global. No caso de uma página html, isso será o objeto window.
Entendendo o básico do uso do this, podemos aprender a usar dois métodos das funções (funções são objetos, então também podem ter métodos, lembra?): call e apply.
O call me permite chamar uma função especificando qual vai ser o objeto this dela; veja o exemplo abaixo
function mudaCor(cor){
this.style.background = cor;
}
mudaCor.call(botao1, "red"); //this dentro de mudaCor vai ser botao1, que fica vermelho...
mudaCor.call(botao2, "green"); //..e assim por diante
mudaCor.call(botao3, "blue");
O segundo método, apply, é parecido, mas ele me permite passar um array de argumentos para a função:
function mudaCor(cor, largura, altura){
this.style.background = cor;
this.style.width = largura;
this.style.height = altura;
}
mudaCor.apply(botao1, ["red", "120px", "20px"]);
Se eu estiver dentro de outra função eu também posso fazer função.apply(objeto, arguments), passando os argumentos recebidos à frente.
Como funções são objetos, eu posso sobrescrever uma função já existente simplesmente assinalando um novo valor para ela. Embora seja perigoso fazer isso em qualquer lugar, podemos tomar vantagem disso e de closures para criar um override de uma função do DOM:
document.createElement = (function (fn)
{
return function (type, id, className)
{
var elem = fn.call(document, type);
if (id) elem.id = id;
if (className) elem.className = className;
return elem;
};
})(document.createElement);
Vamos entender o que rolou aqui: a função document.createElement original aceita apenas um parâmetro, o tipo de objeto (div, span etc), mas eu quero criar uma nova versão da função que aceite também o id e a classe do elemento.
Então eu assinalei o document.createElement a uma função anônima auto-executável, passando como parâmetro a função original (linha 11). Ao ser executada essa função retorna uma nova função, que aceita os três parâmetros que eu quero ter, e que atribui id e className, caso eles tenham sido passados. Eu uso call na linha 5 para garantir que o contexto dessa nova função será o mesmo da função original, no caso, document.
Vale a pena perder um tempinho tentando entender o que aconteceu aqui
Essas 11 linhas mostram bem o quão flexível são funções em JavaScript!
Trabalha com JS? Então porque não aprende? :) (parte 1)
Ontem escrevi sobre closures com JavaScript, e o post foi muito bem recebido. A maioria dizendo que já usava isso de uma forma ou outra, só não sabia o nome e a definição.
Aliás isso é algo muito comum tanto em JavaScript quanto em CSS: todo mundo usa, mas pouca gente realmente fala “vou parar para estudar isso”. Nosso foco costuma ser na tecnologia do lado do servidor, C#, Java, etc. No lado do cliente a gente se vira e vai tocando!
O objetivo desse post dessa série, então, é dar um tour rápido e rasteiro nas características da linguagem JavaScript, para aprendizado ou revisão. A maior parte do que vou escrever aqui eu tirei direto de dois livros: JavaScript – The Definitive Guide, e JavaScript: The Good Parts, ambos da O’Reilly. O segundo é um livro bem fino, menos de 200 páginas, e simplesmente imperdível.
Números
JavaScript só tem um tipo de número: não diferencia entre float, int, decimal, nada. Tudo é armazenado como ponto-flutuante de 64 bits, equivalente ao double de Java e C#. Logo, pro JavaScript 11 e 11.0 são a mesma coisa. Como o Crockford alertou na palestra dele na QConSP, isso também significa que 0.1 + 0.2 não é exatamente igual a 0.3 (0.1 + 0.2 == 0.3 retorna falso). Cuidado com isso!
Podemos representar números com notação cientifica, logo 1000 pode ser escrito como 1e3.
A linguagem também oferece o objeto Math com operações matemáticas como Math.floor, Math.PI e Math.random().
A função parseInt serve para converter uma string para número; mas cuidado com erros como o abaixo:
parseInt("123") //123
parseInt("010") //8, WTF???
parseInt("010", 10) //10
Se o primeiro caracter na string for zero, parseInt vai supor que a base a ser usada é octal, não decimal. Então é sempre bom usar o segundo parâmtro da função, que define a base.
O valor NaN é especial: significa Not a Number, ainda que se fizermos typeof(NaN) retorne “number”. Coisas de JavaScript.
NaN é produzido quando tentamos converter um valor inválido para numérico:
parseInt("Ola", 10) //NaN
O NaN também é contagioso: NaN+10 resulta em NaN. A função isNaN(valor) informa se o mesmo é ou não NaN. Já se fizermos uma divisão por zero, teremos o resultado “Infinity”. Um número negativo dividido por zero retorna “-Infinity”. Infinity também é contagioso.
Strings
Strings podem ser delimitadas por aspas duplas ou simples. Não existe o tipo char, então um caracter é simplesmente uma string de comprimento 1.
Podemos especificar caracteres unicode com \u, então “A” pode ser escrito como “\u0041”.
Como strings são quase-objetos (veremos mais sobre isso depois), podemos fazer coisas do tipo
"Bom dia".length //7
"Crock"[4] //k
"Javascript".charAt(2) //v
"Hello".toUpperCase().substring(1,3) //EL
Valores booleanos, truthy e falsy
Os valores booleanos no JavaScript são true e false. Mas qualquer valor pode ser colocado em uma expressão booleana e ser avaliado como “meio-que-verdadeiro” ou “meio-que-falso”, ou, para usar um termo muito comum em textos sobre JavaScript, truthy e falsy.
Ou seja, os valores abaixo, quando fazem parte de uma expressão booleana, resultarão em false, ou seja, são falsy:
true && 0 //false
true && NaN //false
true && "" //false
true && null //false
true && undefined //false
Por isso que vemos muito código do tipo if (!valor) {…} ao invés de if (valor==null) {…}.
Todos os outros valores são avaliados como truthy.
Se temos um valor qualquer e queremos armazenar o seu valor booleano, podemos então fazer uma dupla negação
!!"" //false
!!"a" //true
Declaração de variáveis e escopo
Declaramos uma variável com o comando var. Mas, se esquecermos de colocar var, essa variável simplesmente será declarada como global. Isso é a causa de muitos bugs, então sempre lembre de usar var, mesmo que você queira (sabe-se lá porquê) ter uma variável global mesmo.
Uma variável declarada mas sem valor atribuido tem o valor undefined.
Uma pegadinha comum no JavaScript é sobre o escopo de variáveis declaradas dentro de um bloco. Observe o código abaixo:
if (true) {
var x = 12;
}
alert(x) //12
Em linguagens como C#, teríamos um erro ao tentarmos acessar a variável x fora do if: qualquer variável declarada dentro de um bloco com chaves { e } tem escopo local a esse bloco; não em JavaScript! O escopo de uma variável em JavaScript é sempre o mesmo: a função dentro da qual ela foi declarada. E não faz diferença declarar todas as variáveis no início da função ou próximo de onde as mesmas são usadas, a alocação de memória será o mesmo. Por isso recomenda-se, por questão de clareza, que todas variáveis sejam declaradas no topo da função.
Objetos
Os valores simples da linguagem JavaScript são number, string, boolean, null e undefined. Todo o resto são objetos. Números, strings e booleanos podem ser tratados como objetos, por terem métodos, mas são imutáveis.
No JavaScript objetos são simplesmente uma coleção de chaves e valores, tal como dicionários ou hashes em outras linguagens. Como objetos podem ser valores de outros objetos, fica fácil criar um grafo de objetos.
Objetos também possuem protótipos, que veremos mais tarde.
Como objetos são simplesmente uma coleção de valores, podemos inicializar um objeto assim:
var meuObjeto = {};
var pessoa = {
nome: "Rodrigo",
emails: {
trabalho: "rodrigo.vieira@trabalho.com",
casa: "rodrigov@casa.com.br"
}
};
Depois de criado podemos adicionar livremente novos atributos a um objeto. Podemos referenciar atributos com duas notações: usando ponto ou colchete. A segunda notação permite que usemos valores definidos em tempo de execução, bem como valores inválidos com a notação com ponto
pessoa.telefone = "8832-3843";
pessoa["#"] = "qualquer coisa"; //pessoa.# daria erro
É possível iterar sobre todos os atributos de um objeto:
var pessoa = { nome: "Rodrigo", telefone: "3727-4882"};
for (var atributo in pessoa) {
alert(pessoa[atributo]); //Rodrigo, 3727-4882
}
Se a gente tenta acessar um atributo que não foi declarado em um objeto (por exemplo pessoa.HJgh), não teremos erro de runtime, simplesmente retornará undefined.
Outro aspecto interessante sobre objetos é a forma como ocorre herança, por protótipos. Mas isso fica para um post separado.
Vetores
Vetores (Arrays) em JavaScript não são arrays “de verdade”, ou seja, não são uma alocação linear de memória de acesso rápido. No JavaScript arrays são basicamente objetos em que as chaves são números inteiros sequenciais, iniciando com zero. Arrays são declarados e usados assim:
var a = [];
a[0] = "Hello";
a[1] = "World"
alert(a[3]); //undefined
Podemos misturar tipos diferentes em arrays:
var b = ["x", 4, { nome: "Rodrigo"}]
O tamanho de um array é sempre definido de acordo com o índice do último elemento presente. Repare no código abaixo:
var x = [];
x[50] = "Steve Jobs";
alert(x.length); //51
alert(x[0]); //undefined
Podemos adicionar um elemento ao fim de um array de duas formas
var x = [];
x[x.length] = "Linus"; //primeira forma
x.push("Torvalds"); //segunda forma
alert(x[0]); //Linus
alert(x[1]); //Torvalds
…e podemos retirar elementos de duas formas
var x = ["Bill", "Gates", "Steve", "Jobs"];
x.pop(); //primeira forma
alert(x.length); //3
x.splice(1,1); //segunda forma -> ["Bill", "Steve"]
x.delete(0); //[undefined, "Steve"];
alert(x.length); //2
O método splice aceita dois argumentos: o primeiro informa o índice a iniciar a retirada de valores, e o segundo o comprimento dessa retirada. Logo se fizermos x.splice(0, x.length) teremos um array vazio como resultado (mas seria mais fácil fazer x = [], claro!). Já o método delete transforma o item em undefined, mas não retira a posição do array. Deixa apenas um “buraco” com undefined.
Quando queremos iterar sobre os elementos de um objeto, vimos que podemos usar o comando for…in, mas no caso de arrays isso não deve ser feito. Isso porque esse tipo de iteração não garante a ordem na qual tais itens serão trazidos, e pode trazer atributos que fazem parte do protótipo de array.
Então o jeito certo de iterar em um array é igual em linguagens como C:
x = [ 1, 2, 3, 4];
for (var i=0; i<x.length; i++){
//....
}
//podemos melhorar um pouco o desempenho guardando o valor computado de x.length
for (var i=0, len=x.length; i<len; i++){
//....
}
Bem, por hoje é só! Na parte 2 veremos o componente mais importante do JavaScript, funções, bem como herança por prototipação, que é bem interessante.
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.