Funções

Uma função é um bloco de código, com nome, que executa uma tarefa e que pode ou não retornar um resultado.

Declaração de funções

Uma função é declarada usando a seguinte sintaxe:

function nome(parâmetro, parâmetro2, parâmetro3,…) { 
   // corpo da função 
}

Os parâmetros de uma função são opcionais podendo a função não ter qualquer parâmetro.

O corpo da função é o conjunto de instruções entre as chavetas da função e que são executadas quando a função é chamada.

Uma função pode ou não retorna um valor. Se retornar, fá-lo com a instrução return que, por sua vez, termina a execução da função.

Exemplo:

function somar(nr1, nr2){ 
   let resultado = nr1 + nr2; 
   console.log( resultado ); 
}

Hoisting de funções

O Javascript move automaticamente as declarações de funções para o topo do código. Chama-se a isto hoisting de funções. Isto permite chamar uma função antes de ela ser declarada:

add(); 

function add(){ 
   console.log(7 + 4); 
}

Parâmetros de uma função

Podemos invocar uma função com um número de argumentos inferior ao número de parâmetros. Nesse caso, o valor dos argumentos em falta será undefined:

function soma(a, b, c){ 
   //função com 3 parâmetros 
   console.log(a, b, c); 
} 

soma(3, 4); //função chamada só com 2 argumentos

O output deste código será: 3
4
Undefined

Parâmetros com valor de defeito

As funções podem ter parâmetros com valores de defeito sendo que, nesse caso, se não fôr passado nenhum valor para esse parâmetro, ele assume o valor de defeito:

function soma(x, y, z=4){ 
   return x + y + z; 
} 

let r = soma(2, 3); console.log(r);

Neste exemplo o parâmetro z assume o valor de defeito, 4, uma vez que na chamada da função não foi passado qualquer argumento para esse parâmetro.

O output do exemplo é :
9

Chamada de uma função

Uma função é chamada pelo seu nome seguido de (), com ou sem argumentos no interior dos parenteses e, desta forma, o código do corpo da função é executado:

somar();  //chama a função somar().

Se a função retornar um valor, ele é recebido quando se chama a função e, normalmente, é guardado numa variável:

//chama a função somar() e guarda o valor por ela retornado na variável resultado:
let resultado = soma();  

Funções que retornam um valor

Uma função pode ou não retornar um valor. Se retornar, fá-lo com a instrução return.
A instrução return faz duas coisas :

  • Pára a execução da função.
  • Retorna um valor para a instrução que invocou a função.
    Exemplo:
function somar(nr1, nr2){ 
   let resultado; 
   resultado = nr1 + nr2; 
   return resultado; 
} 

let res = somar(4, 3); //chama a função somar()e recebe o resultado

No exemplo acima, a função somar() é invocada, a instrução return devolve o resultado e este é recebido na instrução que invocou a função e guardado na variável res.

Funções e this

A palavra reservada this refere-se ao objeto a que ela pertence.

Numa função, a palavra reservada this refere-se ao objeto global que no caso de um browser é o objeto window:

function f(){ 
   alert(this); 
} 

f();

O output deste Código será a seguinte janela de alerta:

No caso de uma função que seja um método de um objeto, this refere-se a esse objeto:

var pessoa = { 
   nome: 'Xico', 
   morada: 'Lisboa', 
   estuda: function(){ 
      alert(this.nome); //this refere-se ao objeto pessoa 
   } 
} 

pessoa.estuda();

O output deste código será a seguinte janela de alerta:

Funções anónimas

Quando declaramos uma função, atribuímos-lhe um nome que usamos para a chamar.
No entanto, em certas situações, podemos criar uma função que não tenha nome, ou seja, uma função anónima.

Funções anónimas atribuídas a uma variável

Podemos criar uma função anónima e atribui-la a uma variável como se de uma expressão se tratasse. A função é invocada através da variável:

let s = function (nr1, nr2){ 
   let resultado; 
   resultado = nr1 + nr2; 
   return resultado; 
} 

//chamamos a função usando o nome da variável a que a função foi atribuída
let res = s(4, 3); 

console.log( res );

No exemplo acima, atribuímos uma função anónima à variável s e invocámos a função através da variável.

Funções anónimas como argumentos de outras funções

Podemos passar uma função anónima como argumento de outra função:

function add(n1, n2, s){ 
   return s(n1, n2); } 
   let res = add(4, 3, function (nr1, nr2){ 
      let resultado; 
      resultado = nr1 + nr2; 
      return resultado; 
   }); 

console.log( "Resultado: " + res );

Neste exemplo o parâmetro s da função add(), recebe uma função anónima que depois será invocada na função add() sendo o resultado devolvido.

Funções que se auto invocam

São funções que se auto invocam e executam automáticamente sem que tenha de haver uma invocação expressa.

NOTA: São conhecidas pela sigla IIFEs (Immediatly Invoked Funtions Expressions).

Exemplo:

(function () { 
   alert("Olá !!"); 
})();

O output deste código será a seguinte janela de alerta:

Funções de seta (arrow functions)

A partir do ES6, surgiu uma nova sintaxe, mais curta, para a escrita de funções: são as funções de arrow (arrow functions).

A sua sintaxe é a seguinte:

(parâmetros) => { instruções }

Nesta nova sintaxe não são necessárias as palavras reservadas function e return. Se as instruções se resumirem a uma única instrução ou expressão, as chavetas e a palavra reservada return podem ser omitidas.

Consideremos a seguinte função:

// função anónima na sintaxe normal 
let f = function(n1, n2) { 
   return n1 + n2; 
} 

let res = f(4, 3);

A função anónima acima pode ser codificada na forma de função de seta:

// função de arrow 
const f = (n1, n2) => n1 + n2;

let res = f(4, 3);

De notar a ausência da palavra reservada function, das chavetas e da palavra reservada return.

Se só houver um parâmetro, podemos eliminar os parenteses curvos:

const r = n => n * 2; 

let res = r(3);

As funções de seta devem ser declaradas antes de serem usadas.

Closures

Uma closure é uma função dentro de outra função que tem acesso às variáveis locais da função externa (mesmo quando a função externa se extingue):

function externa() { 
   let x = 2; 
   return function interna() { 
      return ++x; 
   } 
} 
let f = externa(); 
console.log( f() ); 
console.log( f() );

A função interna() está dentro do escopo da função externa(), logo tem acesso às suas variáveis locais incluindo os parâmetros se os houver.

A função externa() declara a variável x e, no final, retorna a definição da função interna().

Quando chamamos a função externa() e a executamos, estamos de facto a executar a função interna(), que continua a ter acesso à variável local x, da função externa() mesmo quando esta se extingue.

Para comprovarmos isso, as duas instruções

console.log( f() ); 
console.log( f() );

têm como output :
3
4
Ou seja, a variável x, teve um comportamento semelhante a uma variável estática.

Funções construtoras

Um função pode ser um construtor de um objeto. Neste caso, o nome da função deve ter a primeira letra em maiúsculas.

Para criarmos um objeto, chamamos a função com a palavra reservada new.

Exemplo:

function Aluno(nrAluno, nome, turma){ 
   this.nrAluno = nrAluno; 
   this.nome = nome; 
   this.turma = turma; 
} 

let aluno = new Aluno(1, 'Xico', 'Lisboa');

Na prática, as funções construtores atuam como classes.

this

Quando criamos um objeto usando uma função construtora a palavra reservada this refere-se ao próprio objeto.

Prototypes em funções construtoras

Quando uma função é criada, o Javascript adiciona uma propriedade prototype à função. Esta propriedade é um objeto (objeto prototype) que tem uma propriedade constructor que aponta à própria função.

Considere-se a seguinte função construtora:

function Cat(nome, idade) { 
   this.nome = nome; 
   this.idade = idade; 
}  

Para acedermos à propriedade prototype da função fazemos:

console.log(Cat.prototype);

o output é:

Podemos ver duas propriedades no objeto prototype da função Cat(): constructor e proto.

A propriedade constructor aponta à própria função Cat().

A propriedade proto tem a ver com herança e não é discutida aqui.

Agora criemos um objeto com a função construtora Cat():

let c1 = new Cat('xico', 4);

façamos

console.log(c1);

o output será:

Ou seja, veremos o objeto c1 com as sua propriedades e uma propriedade proto.

Esta propriedade proto tem um objeto que aponta para Cat.prototype.

Tendo a propriedade Cat.prototype um objeto, podemos criar dinamicamente propriedades e métodos nesse objeto:

Cat.prototype.raca = 'siames';

Adicionámos a propriedade raca ao objeto prototype de Cat().

Criemos dois objetos c1 e c2:

let c1 = new Cat('messi', 3); 
let c2 = new Cat('ronaldo', 4); 

Se visualizarmos os dois objetos com console.log() teremos o seguinte output:

Ambos “herdaram” a propriedade raca do objeto Cat.prototype !!!

Se fizermos

console.log(c1.raca) ou console.log(c2.raca), teremos em ambos o output siames

Isto porque o Javascript vai procurar a propriedade raca ao objeto e, se não a encontrar, vai procura-la à propriedade proto.