NOTA: Esse artigo não está concluído e pode conter erros.

1. Intro

Funções são objetos de primeira classe em JavaScript. Podem ser:

  • Passadas como parâmetro para outras funções.

  • Retornadas de outras funções.

  • Assinadas à variáveis e propriedades de objetos.

  • Armazenadas em arrays e objetos.

  • Criadas dinamicamente.

  • etc…​

Função helper para não precisar ficar digitando console.log o tempo todo…​

1 2
// Assume ES5 ou mais. console.log.bind(console);
Os exêmplos mostrados não possuem checkagem de erros. Por exemplo, em uma função soma, assume-se que são passados números, e não é feita uma verificação antes de tentar somar os valores. O objetivo é falar das funções em JavaScript e suas peculiaridades.

2. Function Declarations

A sintaxe básica para se criar funções é:

1 2 3
function nomeDaFuncao(zero, ou, mais, argumentos) { // Code goes here... }

Exemplos.

Mostra uma mensagem

1 2 3 4 5 6 7 8 9
function msg() { l('Hello World.'); } // // Chama a função. // msg(); // → Hello World.

Soma, motra o uso de argumentos

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
/** * Define uma função que soma os argumentos num1 e num2 e retorna * o resultado. */ function soma(num1, num2) { return num1 + num2; } // // Cham a função, usando `l` para mostrar o valor retornado. // var total = soma(2, 5); l(total); // → 7 // // O exemplo acima poderia ser reduzido para: // l(soma(2, 5)); // → 7

Sem supresas até aqui, não é? Os exemplos mostrados são muito semelhantes em sintaxe e em funcionamento às funções em outras linguagens.

2.1. arguments

arguments é um parâmetro (um pseudo-array) disponível automaticamente em todas as funções. Ele não é um array pois não tem as propriedades e métodos que objetos do tipo array possuem, mas pode-se acessar seus elementos com subscript notation. Daí a descrição pseudo-array.

Toda vez que você passa parâmetros para as funções, esses parâmetros sempre podem ser acessados através do parâmetro arguments. Por exemplo, na função soma, num1 e num2 podem também ser acessados através de arguments[0] e arguments[1].

Soma com o pseudo-array arguments

1 2 3 4 5
function soma(num1, num2) { return arguments[0] + arguments[1]; } l(soma(3, 1)); // → 4

Outra curiosidade é que se uma função é definida esperando dois argumentos, e no momento da chamada apenas um argumento é passado, o argumento que não foi passado recebe o valor undefined.

1 2 3 4 5 6 7 8
function testeArgs(foo, bar) { l(foo); l(bar); } testeArgs('the force'); // → the force // → undefined

Por outro lado, se a função é definida sem esperar argumentos, ainda assim pode-se chamar a função passando qualquer número de argumentos. Nesse caso, não será possível acessar os argumentos dentro da função pelos seus nomes, pois eles não foram declarados na definição da funnção. Veja:

1 2 3 4 5 6 7 8 9 10 11
function testeArgs() { // // Se chamar a função passando argumentos, como acessar eles, se não // temos seus nomes definidos dentro de ()? // } // // Chama a função passando três argumentos. // testeArgs('Hello', 3.14, [10, 20]);

Nesse caso, o pseudo-array arguments é muito útil.

Função que soma zero ou mais valores

1 2 3 4 5 6 7 8 9 10 11 12 13
/** * Função que soma zero ou mais argumentos. */ function soma() { var total = 0; for (var i = 0; i < arguments.length; ++i) { total += arguments[i]; } return total; } l(soma(3, 1, 5)); // → 9

E nada impede que se misture parâmetros com nomes, e parâmetros opcionais acessados através de arguments.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
/** * Um exemplo bem simples com o puro objetivo de mostrar o conceito. */ function doMath(operador /* 1 ou mais argumentos */) { var total; // // NOTA: // // i inicia a partir do 1 para pular o operador. // if (operador === '+') { total = 0; for (var i = 1; i < arguments.length; ++i) { total += arguments[i]; } } else if (operador === '*') { // // Multiplica o primeiro valor por 1, o que é igual ao próprio // valor. Se multiplicar por 0 sempre vair retornar zero no final. // total = 1; for (var i = 1; i < arguments.length; ++i) { total *= arguments[i]; } } return total; } l(doMath('*', 3, 2, 4)); // → 24 l(doMath('+', 3, 2, 4)); // → 9

3. Function Expressions

Para não confundirmos, já vimos como criar function declarations, que são quelas cujo stement inicia com a keyword function:

function foo() {
    // ...
}

Function expressions são aquelas que aparecem como parte de uma expressão maior. Alguns exemplos são:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
// // 1. Função como event handler. // botao.onclick = function(evt) { // faz alguma coisa quando o user clicar. }; // // ou // botao.addEventListener('click', function(evt) { // faz alguma coisa quando o user clicar. }, false); // // 2. Função como argumento para ordenar um array (de forma // descendente nesse caso). // var arr = '10 52 25 13 14'.split(' ').sort(function(a, b) { return (a - b) * -1; }); // // 3. Construtor. // // Essa é uma _constructor function_, mas ainda assim uma função. // A definição é uma _function declaration_, mas o uso é geralmente // feito em forma de _function expression_. // function Jedi(name, skill) { this.name = name; this.skill = skill; // // 4. Método (1) // this.power = function() { l(this.name + ' uses the force to fight Darth Sith.'); }; } // // Note que a função é usada como parte de uma statement maior, o // que a torna uma _function expression_, e portanto utiliza-se // ponto e vírgula no final. var jedi = new Jedi('Master Yoda', 'The Force'); // // 5. Função usada para se criar escopo de forma a não poluir // o espaço global com variáveis desnecessárias. // (function(msg) { var i, n; i = 1, n = 2; l(msg); l(i - 2); }("Isn't this cool?!")); // // E muitos outros que poderiam ser citados aqui. //
1 Tecnicamente não criamos métodos em JavaScript. O que foi feito nesse caso é que um atributo foi criado, e uma função anônima foi assinada a esse atributo. No entanto, o efeito é o mesmo de ter-se criado um método como em outras linguagens e não tem problema em dizer que criamos um método.

Function expressions são expressões, e como qualquer outra expressão, é aconselhável finalizar a statement com ponto e vírgula. JavaScript possui um mecanismo chamado automatic semicolon insertion (ASI), que permite omitir ponto e vírgula em vários lugares. No entanto, há situações em que o não uso do ponto e vírgula para finalizar satements causa problemas, e em geral é melhor utilizar ponto e vírgula sempre.

4. Immediately Invoked Function Expressions

Immeditely Invoked Function Expressions ou IIFEs, são aquelas definidas e chamadas em um único passo, no mesmo local do código, ao invés de definir a função, e chamar depois, em algum outro ponto do código.

Todas as IIFEs function expressions, pois não é possível definir e já chamar uma function declaration. Isso só é possível com function expressions.

IIFE 1, usada para criar escopo.

1 2 3 4 5 6 7
(function() { (1) // // O código dentro desta IIFE tem escopo limitado pelo bloco // da função e isso evita poluir o espaço global. Essa abordagem // é útil em certas situações, e vale a pena ter sempre em mente. // }()); (2)
1 Para a engine JavaScript, sentenças que iniciam com a keyword function se tratam de function declarations. O ( no início da linha é o que torna a função em uma _function expression, já que a engine JavaScript precisa de algum meio de saber que a função não é uma function declaration. Outros caracteres poderiam ser usados mas cercar a função toda com ( …​ ) é o método mais comumente utilizado.
2 Note o () logo após o }. Isso é o que chama a função logo após sua definição.

Outros exemplos de forçar a engine a tratar o bloco coma uma function expression:

1 2 3 4 5 6 7 8 9 10 11 12 13
true && function() { // body }(); !function() { // body }(); -function() { // body }(); // e vários outros...

4.1. Immediately Invoked Anonymous Function Expressions

Uma IIFE pode ser tanto uma função com nome, quanto uma função a anônima. Se for anônima, será uma IIAFE.

Devemos tomar cuidado para não confundir uma função anônima com uma função anônima imediatamente invocada!

Função Anônima (que não é imediatamente invocada)

1 2 3
telefone.addEventListener('keypress', function(evt) { l('O evento keypress foi disparado...'); }, false);

No exemplo acima, o segundo parâmetro para addEventListener é uma função anônima. No entanto, ela não é chamada imediatamente. Pelo contrário, ela é definida, mas fica aguardando o acontecimento do evento keypress para que o runtime JavaScript chame a função. Entendeu? Essa função é chamada não pelo nosso próprio código, mas sim pela engine JavaScript. Nos definimos a função, mas nunca a chamamos explicitamente via código.

Funnção Anônima Invocada Imediatamente (IIAFE)

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
var meuModulo = (function() { // // Código com visibilidade ofuscada. Ninguém de fora // dessa função consegue acessar. // return { y: 10, x: 15 }; }()); l(meuModulo.x); l(meuModulo.y); // → 10 // → 15

Veja o par extra de parênteses na linha 11, o qual responsável por chamar/invocar a função anônima imediatamente. IIAFE são úteis em alguns casos, como por exemplo, usar uma função com o propósito único de conter escopo.

4.2. Immediately Invoked Named Function Expressions

A utilização de uma IINFE é indicada nos mesmos casos que uma IIAFE, e a funcionalidade é idêntica. A vantagem de se usar uma IINFE em vez de uma IIAFE é que uma função com nome facilita fazer debug, pois erros e outras mensagens muitas vezes vão mencionar o nome da função que originou a mensagem.

1 2 3 4 5 6 7 8 9 10 11
(function limitScope() { // Code goes here. }()); var myModule = (function createModule() { // code... return { // Coisas para expor para o mundo externo. }; }());

4.3. Uso de Named Functions

Muitos de nós tem o costume de usar funções anônimas toda vez precisamos passar uma função como parâmetro. Veja.

Exemplos com funções anônimas

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
jQuery.ajax({ type: 'POST', url: 'libs/ajax.php', data: objeto, success: function(res) { l(res); } }); elem.addEventListener('click', function() { l('elem was clicked'); }, false); arr.sort(function(a, b) { return (a - b) * -1; });

Mas os exemplos abaixo produzem os mesmos resultados, com a vantagem de facilitar o debugging.

Exemplos com funções nomeadas

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
jQuery.ajax({ type: 'POST', url: 'libs/ajax.php', data: objeto, success: function trataResposta(res) { l(res); } }); elem.addEventListener('click', function trataClickUsuario() { l('elem was clicked'); }, false); arr.sort(function ordenaDescendente(a, b) { return (a - b) * -1; });

Isso não quer dizer que quem usa funções anônimas está errado, é mau programador ou coisa assim, e que sempre devemos utilizar funções com nomes. Apenas estou dizendo que as funções nomeadas tem essa vantagem para debugging. Cabe ao desenvolvedor conhecer as opções, e usar aquilo que julgar mais adequado para cada situação.

5. Hoisting

Você sabia que o código digitado nos exemplos anteriores é na verdade “reordenao” pela engine JavaScript antes que sua execução seja iniciada? Sim. É verdade. Esse processo é conhecido como hoisting (não consegui traduzir para pt-br de maneira satisfatória).

Hoisting é um processo de reorganização de código. Todas as definições e declarações que acontecem pelo decorrer do arquivo fonte são movidos para o topo e tem valores temporários de undefined. Somente no momento de sua utilização é que uma variável tem seu valor realmente atributído. Isso tem diversas consequências, como veremos no decorrer deste texto.

Por exemplo.

1 2 3 4 5 6 7 8 9 10
// linhas e linhas // de // código ... ... var foo = 'Yoda'; ... // mais código ... l(foo);

É transofrmado, mais ou menos em algo assim:

1 2 3 4 5
var foo = undefined; ... ... foo = 'Yoda'; l(foo);

Note que a definição de foo é movida para o topo, e tem o valor inicial de undefined. Apenas logo antes de sua utilização o seu valor passa a ser 'Yoda'.

Outro caso bem interessante é o que acontece com funções. O código a seguir

1 2 3 4 5
... ... function foo() { // body of the function }

é transformado em algo mais ou menos assim:

1 2 3 4 5
var foo = function foo() { // body of the function }; ... ...

O resultado é uma function expression.

NOTE que o processo de hoisting sempre move as declarações para o topo e inicializa as variáveis com undefined.

NOTE ainda que function expressions não passam por esse processo o hoisting não acontece.

Veja mais este exemplo:

1 2 3 4 5 6 7 8 9 10 11 12
l(typeof foo); // → undefined // // Essa function expression não passa pelo processo de hoisting. // var foo = function foo() { l('hello'); }; l(typeof foo); // → function

<!-- === Conclusão -→