@rodbv

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

com 5 comentários

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!

Escrito por rodbv

17/09/2010 às 21:53

Publicado em Javascript, Programação

5 Respostas

Assinar os comentários com RSS.

  1. Rodrigo, legal mesmo cara.

    Sempre fiquei muito mais com o backend dos sistemas então não sou um grande conhecedor da parte da interface, mas essa série tá despertando o interesse! :)

    Abraço.

    Zé Filipe

    José Filipe

    18/09/2010 em 00:35

  2. Cara esse foi o melhor artigo dos tres, aprendi muita coisa nova, to doido para usa-las.
    Tenho nem paralavras para agradecer

  3. Valeu Rodrigo! Muito bom mesmo, fico no aguardo da continuação;

    Edmilson

    Edmilson

    18/09/2010 em 18:01

  4. Parabéns por esta série de artigos.

    Por ser uma linguagem que ganhou fama no desenvolvimento front-end, muitos desenvolvedores torcem o nariz para o javascript. É só conhecer iniciativas como http://nodejs.org/ para perceber o quanto é uma linguagem poderosa.

    Humberto

    31/08/2011 em 02:58

  5. Excelente série, parabéns pelos ótimos artigos.


Deixe uma resposta

Preencha os seus dados abaixo ou clique em um ícone para log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Sair / Alterar )

Imagem do Twitter

You are commenting using your Twitter account. Sair / Alterar )

Foto do Facebook

You are commenting using your Facebook account. Sair / Alterar )

Connecting to %s

Seguir

Obtenha todo post novo entregue na sua caixa de entrada.