Funções – JavaScript

Funções em JavaScript são um conjunto de instruções que realizam uma tarefa.

JavaScript é uma linguagem multiparadigma e, dentre eles, faz uso também do funcional. Por isso, funções são uma das coisas mais importantes na linguagem.

Em JavaScript, funções são "cidadãos" de primeira classe. Esse é um termo para dizer que funções recebem o mesmo tratamento que os objetos em JavaScript. A ideia é que, como os objetos são a parte mais importante da linguagem, as funções se equiparam a eles.

Em outras palavras, funções podem ser submetidas às mesmas coisas que um objeto, ou seja, podem ser atribuídas a variáveis, arrays e propriedades de outros objetos, passadas como argumentos para funções, retornadas como resultado de outras funções e ter propriedades, sendo criadas e atribuídas dinamicamente.

Estrutura de Funções

Funções JavaScript, em geral, são formadas pelas seguintes partes:

Possuem um corpo, local onde colocamos as instruções que, quando executadas, realizam a tarefa que a função se propõe;

Dispõem de um sinal gráfico ou palavra chave que identifica determinado código como sendo uma função;

Apresentam formas para receber argumentos, que são as entradas de que as funções podem precisar para realizar suas tarefas e devolver um resultado.

Falando em devolver resultados, toda função JavaScript retorna um valor. Vamos ver no decorrer do artigo, quais podem ser eles para cada situação.

Definindo Funções

Temos algumas possibilidades para criar funções em JavaScript. A mais comum e, digamos também, conhecida entre elas, é a que recebe o nome formal de definição ou declaração de função.

Essa forma de criar uma função, consiste em: usar a palavra chave function, atribuir um nome para a função, passar os argumentos – entre parênteses e separados por vírgula – para dentro da função, quando for o caso, e definir o bloco, através do sinal abre e fecha chaves { }, para representar o corpo da função, local onde estarão as instruções que ela executará.

No exemplo abaixo, declaramos uma função denominada soma, que recebe dois números como argumentos, e retorna a soma entre eles.

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

Expressão de Função

Quando escrevemos algo como x = 3, 2 + 3, etc, dizemos que temos uma expressão em JavaScript.

De forma análoga, ao atribuir função a uma variável, temos a chamada expressão de função.

Por exemplo, uma função que recebe dois valores como argumentos, e retorna a subtração dos mesmos.

let subtrai = function(x, y) {
    return x - y;
};

Funções não são objetos de primeira classe? Então! Elas podem ser atribuídas a uma variável.

A diferença dessa, para a forma anterior, é que a função não necessariamente precisa ter um nome. Mas, se tiver, esse nome estará somente disponível no interior da própria função, para se referir a si mesma.

Ou seja, quando você atribui função a uma variável, não é obrigatório que ela tenha um nome.

Quando atribuímos função à uma variável, é através da própria variável que a função fica disponível ao mundo externo. Isso não quer dizer que a variável passa a ser uma função, e sim, que está recebendo essa função.

Expressões de Função Curtas

Podemos também criar as expressões de função curtas. Vulgarmente conhecidas como Arrow Functions. Elas podem ser declaradas de diversas formas.

Primeiramente, no lugar da palavra chave function, usamos uma seta =>, que justamente dá nome a esse tipo de função.

Veja a declaração, usando Arrow Functions, de uma função que retorna o quadrado de um número:

let quadrado = (x) => {
    return x * x;
};

Quando há somente uma instrução no corpo da Arrow Function, podemos omitir a sintaxe de bloco, ou seja, as chaves { }. Além disso, devemos remover também a palavra chave return. Porque, nesse caso, o retorno será feito de maneira implícita.

let quadrado = (x) => x * x;

Nos casos em que uma Arrow Function recebe somente um argumento, os parênteses que o envolvem são opcionais. Por isso, a expressão abaixo também é válida.

let quadrado = x => x * x;

Arrow Functions também só podem ser anônimas. E, quando não recebem argumentos, você deve manter os parênteses designados a eles. Veja o exemplo abaixo.

let novaFuncao = () => 'Eu não recebo argumentos';

Invocando Funções

Agora que vimos como definir funções, vamos aprender a invocá-las. Em outras palavras, chamar a função para que ela execute as ações definidas no momento em que a declaramos.

Funções em JavaScript são invocadas utilizando-se o operador abre e fecha parêntese (). Invocar a função é o que realmente faz com que o JavaScript a avalie como uma expressão, executando as instruções descritas nela.

O nome soma que foi dado para nossa função acima, bem como o da variável subtrai, que recebeu uma função, são os identificadores dessas funções. É através deles que faremos referência a essas funções no futuro.

Quando o interpretador do JavaScript encontra a referência para uma função que anteriormente foi declarada, seguida de parênteses, ele sabe que precisa executar a função que esse identificador referencia.

Ciente disso, podemos invocar a função soma, que escrevemos acima, da seguinte forma:

soma(2, 3); //5

Ou seja, usamos o nome da função e passamos os argumentos, que ela espera receber para que possa realizar seu trabalho, dentro do próprio operador que a invoca.

Da mesma forma, é possível invocar a função que atribuímos anteriormente à variável subtrai. Isso porque, conforme já dito, a referência para aquela função é a variável.

subtrai(8, 2); //6

Invocação Imediata de Funções

Seguindo esse raciocínio, podemos pensar assim: se soma e subtrai são apenas referências para funções que foram definidas anteriormente, então podemos definir uma função sem vínculo à nenhum identificador, e invocá-la imediatamente após sua definição.

function() {
    return 'Função Auto-invocável'
}(); //Tentando invocar a função aqui

/*
Resultado:
SyntaxError: Function statements require a function name
*/

Mas, como você pôde ver, isso gera um erro.

Para entender o motivo, nós precisamos ter em mente o seguinte: em tempo de execução, funções em JavaScript passam por dois estágios.

O primeiro deles, é o da declaração. Nele, a função está sendo construída, baseada nas instruções contidas no código, que definem a mesma.

Após construída, a função já é uma expressão, estando pronta para ser executada. Dizemos então, que ela está no segundo estágio.

Expressão em JavaScript, é algo que pode ser avaliado pelo interpretador, para produzir um valor. Por exemplo: 2 + 3, x = 1, 7, etc.

Em nosso exemplo, quando o interpretador do JavaScript encontra a palavra-chave function, ele entende que precisa definir a função. E, como tal, ela deve ter um nome (identificador).

Nomeando a função, temos agora uma sintaxe válida para a declaração. E o erro que continua sendo apresentado é por um motivo completamente à parte, que explicaremos a partir do próximo parágrafo.

function nomeQualquer() {
    return 'Função Auto-invocável'
}(); //Tentando invocar a função aqui

/*
Resultado
SyntaxError: Unexpected token ')'
*/

Enquanto que parênteses colocados após uma expressão indicam que essa expressão é uma função e deve ser invocada, parênteses colocados após uma declaração são totalmente separados da instrução anterior. Nesse caso, os parênteses representam simplesmente um operador de agrupamento.

Um operador de agrupamento é usado, entre outras coisas, para controlar a precedência de operadores em JavaScript.

Exemplo: na expressão abaixo, será feito primeiro a multiplicação, e depois, a adição.

2 * 3 + 5 //11

Agora, com o operador de agrupamento, dizemos para o JavaScript executar a adição antes da multiplicação.

2 * (3 + 5) //16

Portanto, o erro que continua sendo lançado em nossa declaração acima, é porque um operador de agrupamento não pode estar vazio. A prova disso é que, colocando uma expressão qualquer dentro dele, nenhum erro mais é lançado.

function nomeQualquer() {
    return 'Função Auto-invocável'
}(1);

Tudo bem, mas ainda não saímos da estaca zero. Acabamos declarando uma função nomeada, no estilo já visto lá no início do artigo, e temos também um operador de agrupamento com uma expressão dentro dele. Para o JavaScript inclusive, isso são duas instruções completamente separadas. Ele está enxergando nosso código, como se fosse algo assim:

function nomeQualquer() {
    return 'Função Auto-invocável'
};

(1);

Ok! Mas as coisas já vão fazer sentido.

Para o interpretador do JavaScript, tudo o que estiver dentro de um operador de agrupamento, será considerado uma expressão. Então, uma forma de transformar manualmente uma declaração de função em expressão, é colocá-la dentro de um operador de agrupamento.

Isso diz para o JavaScript que ele deve, em tempo de execução, transformar a declaração em uma expressão.

(function() {
    return 'Função Auto-invocável'
});

Agora que temos uma expressão, podemos invocá-la.

(function() {
    return 'Função Auto-invocável'
})();

Esse tipo de construção recebe o nome de função auto-invocável. Ou seja, ela executa assim que é definida. Em inglês, é também chamada de Immediately Invoked Function Expression (IIFE).

Parâmetros de Funções

Você viu, quando falamos sobre a estrutura de uma função, que ela possui um lugar para receber argumentos. E até já passamos argumentos para funções algumas vezes nos exemplos acima.

Revendo a função soma que já definimos, notamos que ela recebe dois argumentos, e retorna a soma entre eles.

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

Então podemos passar e uma função recebe, argumentos. Mas, aquelas variáveis que estão entre parênteses depois do nome da função, recebem o nome de parâmetros.

Reforçando. Parâmetro é simplesmente uma nomenclatura para diferenciar aquelas variáveis específicas, que estão no enunciado de uma função e recebem os argumentos da mesma. Pense assim: se, quando criamos uma variável, temos uma variável. Aquelas variáveis específicas, recebem o nome de parâmetros.

Objeto Arguments

Os Argumentos que uma função recebe são guardados em um objeto do tipo array chamado arguments. Quando você cria uma função, ela já vem com esse objeto embutido.

Então você pode usar diretamente esse objeto dentro de uma função para manipular os argumentos que ela recebe.

Executando o código abaixo;

function frutas() {
    for (var i = 0; i < arguments.length; i++) {
        console.log(arguments[i]);
    }
}

frutas('Laranja', 'Mamão', 'Abacaxi', 'Melão', 'Banana', 'Maçã', 'Quiuí');

Retorna o seguinte:

Laranja
Mamão
Abacaxi
Melão
Banana
Maçã
Quiuí

Veja que, mesmo a função não recebendo formalmente os argumentos, o objeto arguments tem acesso a eles no interior da função.

O objeto arguments não é um array. Apesar de parecer, pelo fato de possuir a propriedade length, e aquela notação de índice numerado, as semelhanças acabam aí.

Arguments não possui todas as propriedades e métodos de um array. Isso significa, que não podemos usar propriedades e métodos comuns de arrays, como sort, map, forEach, diretamente no objeto arguments.

Arguments é, na verdade, simplesmente uma lista ou coleção de itens.

Operador Rest

A partir do ES6, surgiu o operador rest. Ele é representado por reticências . Esse operador, quando colocado antes do último parâmetro nomeado de uma função, coleta todos os argumentos passados para esse parâmetro e coloca-os em um array.

No exemplo abaixo, o rest coletará, do segundo argumento em diante. Pois o primeiro, foi mapeado para o parâmetro a.

function coleta(a, ...b) {
    console.log(a);
    console.log(b);
}

Executando a função;

coleta(1, 'lala', true, 'sim', 20);

O retorno será:

1
["lala", true, "sim", 20]

O resultado do operador rest sim é um array. Então você pode usar de todo o poder que arrays possuem em JavaScript para manipular esses elementos.

Diferente de arguments, que contém todos os argumentos passados para uma função, o array criado pelo operador rest terá somente os argumentos passados a ele.

Parâmetros Padrão

Outra novidade, surgida no mesmo pacote de especificações ES6, foi a dos parâmetros padrão. Vamos entender a utilidade deles.

Se eu declarar um parâmetro em uma função e não atribuir valor a ele, seu valor padrão será undefined.

function retorna(a) {
    return a
}

retorna(); //undefined

Agora, passando um argumento, ele é atribuído ao parâmetro.

retorna(1); //1

Com a novidade, eu posso definir o valor padrão de um parâmetro.

function retorna(a=2) {
    return a
}

Dessa forma, ao não passar um argumento, o padrão será aplicado.

retorna(); //2

E passando um argumento, ele será atribuído normalmente ao parâmetro.

retorna(5); //5

Tipos de Argumentos que Funções Podem Receber

Acabamos de ver como invocar funções passando como argumentos, valores primitivos. Mas, como funções são especiais. podem também receber objetos e outras funções como argumentos.

Posso criar objetos separadamente, e passá-los depois para dentro da função.

let pessoa = {
    nome: 'Gabriel',
    idade: 20
};

function mostra(argumento) {
    return argumento;
};

mostra(pessoa);

Ou, defini-los diretamente na hora de invocar a função.

mostra({
    nome: 'Fernando',
    idade: 20
});

No exemplo acima, passei um objeto literal para dentro da função mostra. Mas, isso vale também para funções e outros objetos.

Vejamos um exemplo com funções.

A ideia é a seguinte: eu tenho novamente a função mostra, que espera receber como parâmetro outra função, e retorna a execução da mesma.

function mostra(arg) {
    return arg();
};

Defino então a função a ser mostrada.

function boasVindas() {
    return 'Bem Vindo';
}

E passo a função boasVindas no estado literal para dentro de mostra, ou seja, sem invocá-la. Isso porque, a função mostra invoca o que estiver no parâmetro.

mostra(boasVindas); //"Bem Vindo"

Escopo de Funções

Escopos são limitadores de acesso em programação, e podem ser utilizados para estabelecer, por exemplo, que determinados dados não devem estar visíveis para além das fronteiras delimitadas por ele.

Funções criam escopo e, quando invocadas, retornam para o mundo externo, apenas aquilo que estiver indicado através da instrução return.

Por exemplo:

Digamos que eu tenha uma função chamada myFunc e, dentro dela, uma variável myVar, que recebe uma String. Em seguida, retorno essa variável para o mundo externo.

function myFunc() {
    let myVar = 'Variável no escopo de função';
    return myVar;
};

Invocando a função, ela nos devolve como resposta, justamente o que estiver indicado no return, em nosso caso, o conteúdo da variável.

myFunc(); //"Variável no escopo de função"

Se eu tento acessar diretamente myVar, o JavaScript retorna um erro, dizendo que ela não está definida. Pois, justamente, o escopo que a função cria, faz com que a variável não seja visível fora da função.

myVar; //myVar is not defined

Por outro lado, ao declarar uma variável dentro da função, sem utilizar a palavra-chave var ou let, ela será criada no escopo global. Vamos para um exemplo, que é bem mais fácil de entender.

function outraFunc() {
    outraVar = 'Variável no escopo global';
    return outraVar;
};

Tentando acessar a variável fora da função, ela ainda não existe. Pois será criada somente quando invocarmos a função.

outraVar; //ReferenceError: outraVar is not defined

Invocando a função, a variável é criada e retornada.

outraFunc(); //"Variável no escopo global"

Agora que a variável existe, podemos acessá-la de fora da função. Justamente por ter sido criada no escopo global.

outraVar; //"Variável no escopo global"

Ou seja, se você declara uma variável sem utilizar a palavra-chave var ou let, o JavaScript interpreta que essa variável deve ser criada no escopo global.

Esse é um detalhe sutil, mas que você deve tomar bastante cuidado. É altamente recomendável definir variáveis sempre usando var ou let, para que elas não vazem para fora das funções.

Parâmetros de funções também são locais. No exemplo abaixo, temos uma função que recebe um argumento e simplesmente, o devolve.

function devolve(argumento) {
    return argumento;
};

Chamando a função, recebemos como retorno, o argumento.

devolve('Oi'); //"Oi"

Tentando acessar o parâmetro de fora da função, ele não existe. Porque parâmetros de funções também são locais, estando disponíveis somente dentro do escopo da função.

parametro; //ReferenceError: parametro is not defined

Funções criam escopo, certo? Então, se eu defino uma função dentro de outra, também não consigo acessá-la no lado de fora da função hospedeira. Vejamos isso no código.

function func() {

    function funcAninhada() {
        return 'Sou uma função, estou disponível dentro de func.'
    };
};

funcAninhada(); //ReferenceError: funcAninhada is not defined

Ou seja, func é uma cápsula ao redor de funcAninhada.

Mas, funcAninhada existe dentro de func.

function func() {

    function funcAninhada() {
        return 'Sou uma função, estou disponível dentro de func.'
    };

    return funcAninhada();
};

Invocando func, ela me devolve funcAninhada invocada.

func(); //"Sou uma função, estou disponível dentro de func."

Com isso, concluímos que funções precisam estar definidas no escopo onde você deseja invocá-las.

Agora, no interior de uma função, temos acesso à variáveis, parâmetros, funções e objetos definidos no escopo mais amplo.

Por exemplo, na tira de código abaixo, criamos uma variável no escopo global, e fizemos o acesso dela no interior da função.

let varGlobal = 18;

function func() {
    return varGlobal;
};

func(); //18

Isso também vale para múltiplas funções aninhadas em sequência.

Incrementando o exemplo anterior, nossa funcAninhada não deve encontrar problemas para acessar recursos que estão em seu ou no escopo mais amplo.

let varGlobal = 18;

function func(argumento) {
    let varFunc = 2

    function funcAninhada() {
        let varFuncAninhada = 1;
        return varGlobal + argumento + varFunc + varFuncAninhada;
    };

    return funcAninhada();
};

func(3); //24

Isso justamente porque, se uma função está definida dentro de outra, ela pode acessar os recursos da função hospedeira, bem como, de funções que hospedam a hospedeira.

Retorno de Funções

Funções em JavaScript podem retornar basicamente qualquer tipo de dado, incluindo primitivos, funções e objetos;

Entre os primitivos, podemos retornar:

Number;

function returnNumber() {
    return 1;
};

returnNumber(); //1

String;

function returnString() {
    return 'Hello';
};

 returnString(); //"Hello"

Boolean;

function returnBoolean() {
    return true;
};

returnBoolean(); //true

Null;

function returnNull() {
    return null;
};

returnNull(); //null

E Undefined. Aliás, esse é o retorno padrão de uma função. Ou seja, quando não há um retorno explícito na função, ela retorna undefined.

function returnUndefined() {

};

returnUndefined(); //undefined

Podemos retornar também objetos, como arrays, por exemplo:

function myFunction() {
    return [1, 2, 3];
};

myFunction(); //[1, 2, 3]

Ou objetos criados por nós;

function pessoa() {
    return {
        nome: 'Vanessa',
        sobrenome: 'Santos'
    };
};

pessoa(); //{nome: "Vanessa", sobrenome: "Santos"}

Funções também podem retornar outras funções. Veja:

function func() {
    function funcRetornada() {
        return 'Função retornada';
    };
    return funcRetornada;
};

Temos aqui uma função chamada func que, quando invocada, retorna a função funcRetornada no estado literal.

/*Invocação de func*/

func();

/*Retorno

ƒ funcRetornada() {
        return 'Função retornada';
    }

*/

Como agora func() guarda em si uma função literal, podemos invocá-la.

func()(); //"Função retornada"

Outra forma, é atribuir a primeira chamada a uma variável;

let retornoDeFunc = func();

E, como a variável agora contém uma função, invocá-la.

retornoDeFunc(); //"Função retornada"

O return é a última instrução executada em uma função, e especifica o que deve ser retornado para quem a invocou. Isso faz com que o interpretador do javaScript saia da função, imediatamente depois de executar essa instrução, e siga para onde foi o retorno da função. O retorno é dado para quem invocou a função.

Podemos comprovar esse comportamento no código abaixo, onde colocamos duas instruções de retorno em uma função. E o retorno foi, simplesmente, o da primeira.

function testeRetorno() {
    return 1;
    return 2;
};

testeRetorno(); //1

Por esse comportamento, o return também pode ser considerado uma instrução de salto. Visto que, ele pode pular certas instruções em determinadas ocasiões. Vamos entender isso melhor.

Temos abaixo uma função que testa se o argumento recebido por ela, é o número 1.

function testa(x){

    if(x ===1) {
        console.log('x é igual a 1');
        console.log('x é um número');
    return;
    }

    console.log('Se x for igual a 1,');
    console.log('e x for um número,');
    console.log('essas instruções não serão executadas.');
}

Passando 1 para essa função, ela entra no if. Em seguida, retorna para que a invocou – sem executar as instruções que estão abaixo do return. Depois disso, o fluxo de execução continua para a linha seguinte à que fez a chamada da função testa.

Portanto, executando o código;

testa(1);

console.log('Próxima linha de execução, após sair da função.');

Temos a seguinte saída:

x é igual a 1
x é um número
Próxima linha de execução, após sair da função.

Se o retorno é a última instrução executada em uma função, por que então o código abaixo funciona?

function func() {

    return soma();

    function soma() {
        return 2 + 3;
    };
};

func(); //5

Por causa de um comportamento do JavaScript, chamado hoisting. Vamos entender.

Hoisting

O que significa hoisting?

Alguns tradutores de livros sobre JavaScript, traduzem hoisting como içamento ou elevação. Pois é justamente isso o que acontece.

Na hora de interpretar o código, se o JavaScript encontra funções literais, ele as move para o início do arquivo. Então o código que escrevemos acima, é interpretado assim pelo JavaScript.

function func() {

    function soma() {
        return 1 + 3;
    };

return soma();
};

Agora fiz uma pequena alteração no código que tínhamos inicialmente, atribuindo aquela função literal a uma variável.

function func() {

    return sum();

    let sum = function soma() {
        return 1 + 3;
    };
};

Será que ainda funciona?

Agora ele retorna "undefined is not a function". Isso tem a ver com o hoisting de variáveis, que é feito de maneira diferente em relação à funções.

O içamento de variáveis funciona assim: o JavaScript às declara no início do arquivo como undefined, e só faz a atribuição de seu valor real na altura exata do código onde você fez a atribuição a elas.

Nosso novo código é interpretado da seguinte maneira pelo JavaScript:

function func() {

    let sum = undefined;

    return sum();

    sum = function soma() {
        return 1 + 3;
    };
};

Então, de fato, quando o JavaScript executa o return sum(); a variável sum ainda é undefined e não recebeu a atribuição da função soma.

Podemos entender com isso que: variáveis só estão disponíveis a partir do ponto exato onde fazemos sua atribuição. Já funções literais, estão disponíveis em todo o escopo onde estão inseridas, pois são movidas para o início do arquivo.

É óbvio que você não vai definir uma função depois do retorno. Eu só te mostrei isso para você saber que é assim que o JavaScript trabalha e, se você vir um código assim por aí na internet, saber que é válido. O ideal é sempre declarar as variáveis no início do arquivo e os retornos no final para não ter problemas.

Propriedades e Métodos de Funções

Invocando uma função, ela é avaliada pelo interpretador do JavaScript, e nos devolve um retorno. Seja ele o que estiver expresso dentro da função ou o retorno padrão undefined.

function qualquer() {
    return 'Retorno da função invocada';
};

qualquer(); //"Retorno da função invocada"

Por outro lado, se eu não invocar a função, ela é um objeto. A prova disso, é que possui propriedades e métodos.

Por exemplo: a propriedade que retorna o nome de uma função.

qualquer.name; //"qualquer"

Ou ainda, o método que liga a função a um contexto.

qualquer.bind();

Artigos Relacionados