Dê o poder ao homem, e descobrirá quem ele realmente é. - Maquiavel

Esse post tem a finalidade de criar a base necessária para acompanhar o próximo artigo sobre callbacks que estará repleto de aplicações práticas. Aqui vamos abordar alguns conceitos sobre funções que são desconhecidas pela maioria que opta por adentrar em nossa querida linguagem.

Em Javascript, funções são nada mais que objetos. É fácil constatar isso quando você vê que elas podem ser criadas como se fossem instâncias de uma classe:

var sayMessage = new Function("message","console.log(message);");  
sayMessage("Hello!"); // Hello  

Essa simples constatação nos leva a dividir essa aprenstação de funções na seguinte estrutura:

  • First Class Functions
  • Higher Order Functions
  • Function Declarations x Expressions

E de quebra adicionar dois tópicos muito importantes quando se introduz funções em Javascript:

  • Overloading
  • Arguments Object

First Class Functions Support

Quando você fala que uma linguagem de programação suporta "First-Class" functions, você quer dizer que ela trata funções como valores, portanto:

  • Você pode atribuir uma função a uma certa variável.

E é examente isso que a linguagem Javascript é capaz de fazer:

var twice = function(x) {console.log(2*x);};  
twice(7); //14 

Higher Order Functions

Higher Order Functions são funções que possuem, pelo menos, uma das seguintes características:

  • Recebem uma ou mais funções como parâmetro;
  • Retornam uma função;

E, novamente, é examente isso que as funções em Javascript são capazes de fazer:

// Recebe uma função como parâmetro => Higher Order
function inject(f, x){  
  return f(f(x));
}

// Não recebe nem retorna função => Não é Higher Order
function g(x){  
  return x*2;
}

inject(g, 9); // 36  

Nota: É muito comum que os termos "First Class" e "Higher Order" functions sejam usados em substituição um do outro, mas o primeiro é uma característica intrínseca da linguagem, o segundo da função. Assim, se uma pessoa diz que uma linguagem suporta First Class Functions, é possível fazer uso de Higher Order Functions. A segunda vem como decorrência do suporte à primeira.

Declarations x Expressions

Apesar de Javascript permitir você criar uma função usando construtores, as duas formas mais comuns e melhores são, através de declarações e expressões.

Declarações tem um mecanismo de hoisting aplicado ao escopo, permitindo a situação abaixo:

//usa a função add antes mesmo desta ser declarada
// por conta do mecanismo de hoisting
var result = add(5, 5); 

//function declaration
function add(num1, num2) {  
return num1 + num2;  
}

Enquanto as "function expressions" não permitem o mesmo tipo de situação:

// erro, pois não há hoisting em function expressions!
var result = add(5, 5);

// function expression
var add = function(num1, num2) {  
  return num1 + num2;
};

Overloading

A maioria das linguagens orientadas a objeto suportam function overloading que é a habilidade de uma única função possuir várias assinaturas. Assinatura é composta do nome da função mais o número e tipos de argumentos que uma função espera.

Javascript não funciona dessa maneira, pois quando você declara uma função você implicitamente está atribuindo a uma variável de mesmo nome a própria função que declarou, vejamos abaixo:

function multiplica(x,y){return x*y;};  
function multiplica(x,y,z){return x*y*z}  
//implicitamente ocorre
//var multiplica;
//multiplica = function(x,y) {return x * y;};
//multiplica = function(x,y,z){return x*y*z};
console.log(multiplica(2,4)); //NaN  
console.log(multiplica(2,4,8)); //64  

Do exemplo acima, é importante observar duas coisas:

  • A impossibilidade de overloading em Javascript;
  • A possibilidade de forjar overloading em Javascript;

Acontece que funções em Javascript permitem a passagem de quaisquer números de parâmetros, mesmo que na sua assinatura só tenha um. Veja, por exemplo a chamada do multiplica(2,4) na penúltima linha. Apesar da assinatura da função multiplica possuir três argumentos, a chamada com dois argumentos não reproduz erro, apenas executa a função considerando o valor de z "undefined";

Essa capacidade de receber quaisquer argumentos daria ao Javascript a capacidade de forjar overloading, se ele tivesse como saber a quantidade de argumentos passados a ele e o valor de cada um deles. Acontece que sim, o Javascript possui essa capacidade, através dos chamados Arguments object.

Arguments Object

Vamos começar vendo como simularíamos o overloading da função multiplica acima, para que ela funcione para 2 ou 3 argumentos, apesar de sua assinatura conter 3 parâmetros:

function multiplica(x,y,z) {  
     if(arguments.length ===2) {
          return x*y;     
     }
     return x*y*z;  
}
console.log(multiplica(2,4));  //8  - overloading de 2 parâmetros  
console.log(multiplica(2,4,8));  //64  
console.log(multiplica(2,4,8,2));  //64

Agora que você sabe como o Arguments Object pode ser usado, é importante conhecer suas características:

  • Ele é um objeto similar a um Array(Array-like Object);
  • Ele muda de acordo com o escopo da função;

Arguments - Array-Like Objects

Quando você fala que arguments é um objeto, não é a toa: tudo é um objeto em Javascript. Até os valores primitivos são envelopados por objetos(Primitive Wrapper Types). Então dizer que arguments é um objeto não lhe diz muita coisa.

O importante aqui é saber que o objeto "arguments é similar a um array, porém não possui algumas propriedades nativas presentes no prototype do array, como os métodos filter,forEach e reduce, apenas para citar alguns.

Para demonstrar essa semelhança, vamos dar continuidade ao nosso exemplo da função multiplica, mas implementando-a de uma maneira que ela funcione para quaisquer quantidade de parâmetros passados. Vejamos:

function multiplica() {  
     var length = arguments.length;
     var total =1;
    if(length>0) {
          for(var i = 0; i<length; i+=1){
             total = total * arguments[i];
         }
          return total;
     }
    return 0;
}
console.log(multiplica()); //0  
console.log(multiplica(2)); //2  
console.log(multiplica(2,4));  //8  
console.log(multiplica(2,4,8));  //64  
console.log(multiplica(2,4,8,2));  //128  

Arguments - Escopo da Função

Como não poderia ser diferente, vamos tratar novamente de analisarmos escopos. Dessa vez aplicados ao objeto "arguments". O objetivo aqui é apenas fazer com que você fique alerta quando se deparar com situações como a que segue, principalmente em código de terceiros:

function externa(){  
    var externalArguments = arguments;
    function interna(){
        console.log(externalArguments.length);
        console.log(arguments.length); //0
    }
    interna();
}
externa(1,2,3); // 3 e 0  
externa(1); // 1 e 0

Próximos Passos:

Funções são os cavalos-de-força em Javascript. Sem elas e sua incrível flexibilidade, a linguagem estaria morta. Conhecer como elas funcionam e seu diferencial vai lhe ajudar a entender a quebra de paradigma entre as linguagens Orientadas a Objeto e Funcionais. Entendê-las vai lhe ajudar a pensar não mais em termos de objetos, mas em termos de ações.

E é a isso que o próximo artigo se propõe: mostrar as infinidades de casos práticos e enormes ganhos de flexibilidade quando se trabalha com sabedoria em termos de ações.

  • Saiba tudo sobre Callbacks em Javascript.

Veja Também

Sobre o Autor

Johel Carvalho

Johel Carvalho

Engenheiro Civil formado pelo Instituto Militar de Engenharia (IME-RJ) em Dezembro de 2012. Largou a profissão pelo desenvolvimento web, começando com C# e sendo atualmente aficionado por JavaScript.

comments powered by Disqus