View on GitHub

aulas-programacao-web

Materiais de Aula - Programação de Computadores com tecnologias Web

Funções

📽 Veja esta vídeo-aula no Youtube

Por se tratar de uma linguagem dinâmica interpretada o JavaScript permite muita flexibilidade ao trabalhar com funções.

Elas são tratadas como “cidadãos de primeira classe”, o que significa que uma função não é diferente de outro tipo de dado qualquer, podendo ser armazenada em variáveis e passada como parâmetro para outras funções. Essencialmente, uma função armazena um bloco de código JavaScript que pode ser executado a qualquer momento.

Criando funções

Declaramos uma função usando a palavra chave function:

function nomeDaFuncao() {
    // código da função (comandos JavaScript quaisquer)
}

E a executamos usando ():

nomeDaFuncao();

Percebam que referenciar o nome da função sem () retorna o valor da função (seu bloco de código).

Outra maneira de se criar uma função é através da uma expressão. Criamos uma função anônima e atribuímos a uma variável. Posteriomente, podemos manipulá-la como qualquer outra variável, e executá-la normalmente.

let minhaFuncao = function() {
    console.log("Dev Web");
};

🍌 Atente ao ; no final da expressão. Esse comando é uma atribuição normal, não há porque não usar o finalizador de comando.

Expressões são preferíveis à declarações.

Escopo e visibilidade

Variáveis declaradas dentro de uma função só são visíveis dentro da função. São chamadas de variáveis locais.

let exibirMensagem = function() {
    let mensagem = "Olá!";
    console.log(mensagem); // Essa linha não gera um erro, a variável está visível
};

exibirMensagem();

console.log(mensagem); // Essa linha gerará um erro, a variável não está visível

Variáveis externas à função são visíveis internamente.

let mensagem = "Olá!";

let exibirMensagem = function() {
    console.log(mensagem); // Essa linha não gera um erro, a variável está visível
};

exibirMensagem();

console.log(mensagem); // Essa linha não gera um erro, a variável está visível

O acesso é completo, podendo inclusive alterá-las livremente.

let mensagem = "Olá!";

let exibirMensagem = function() {
    mensagem = "Hello!"; // O valor será alterado!
    console.log(mensagem);
};

exibirMensagem();

console.log(mensagem);

Caso seja criada outra com o mesmo nome internamente, ela sobrepõe a externa, que não estará mais visível. Dizemos que a variável foi sombreada.

let mensagem = "Olá!";

let exibirMensagem = function() {
    let mensagem; // Esta variável sombreará a externa!

    mensagem = "Hello!"; // O valor da interna será alterado
    console.log(mensagem); // Hello!
};

exibirMensagem();

console.log(mensagem); // Olá!

Resumindo, variáveis declaradas fora de funções são chamadas de variáveis globais e estão disponíveis em todas as funções, exceto quando sombreadas dentro da função.

Parâmetros e argumentos

Podemos definir parâmetros de entrada, nos quais os argumentos serão copiados.

let exibirMensagem = function(mensagem) {
    console.log(mensagem); // Exibirá o valor recebido como argumento
};

exibirMensagem("Olá!");
exibirMensagem("Hello!");

Todos os parâmetros são opcionais. Podemos definir valores padrão para os parâmetros.

let exibirMensagem = function(mensagem, gritar = false) {
    if (gritar){
        mensagem = mensagem.toUpperCase();
    }
    console.log(mensagem);
};

exibirMensagem("Olá!");
exibirMensagem("Olá!", false);
exibirMensagem("Olá!", true);

Retornos

Não há distinção rígida entre procedimentos e funções em JavaScript. Funções sempre retornam valores, de forma implícita ou explícita. Caso não definamos, será sempre retornado implicitamente o valor undefined.

let exibirMensagem = function() {
    console.log("Olá!");
};

let retorno = exibirMensagem();

console.log(retorno); // undefined, implícito

Criamos retorno explícitos usando o comando return. Ele imediatamente finaliza a função, retornando o valor indicado. Se nenhum valor for indicado, será retornado undefined.

let exibirMensagem = function() {
    console.log("Olá!");
    return; // Será retornado undefined
    console.log("Este comando nunca será executado!");
};

let retorno = exibirMensagem();

console.log(retorno); // undefined

Podemos ter vários pontos de retorno em nossa função.

let verificaParidade = function(numero) {
    if (numero % 2 !== 1) {
        return "par";
    } else {
        return "ímpar";
    }
};

console.log(verificaParidade(3)); // ímpar
console.log(verificaParidade(0)); // par
console.log(verificaParidade(-2)); // par
console.log(verificaParidade(6)); // par

Arrow functions

Há também uma notação diferenciada para a definição de funções chamada arrow function, devido ao seus operador => se assemelhar a uma seta (arrow). Essa notação simplifica a definição de funções com retorno definido. Veja o exemplo:

let soma = function(a, b) {
    return a + b;
};

console.log(soma(1, 3)); // 4
console.log(soma(32.4, -2)); // 30.4

Nessa situação, usar uma arrow function é bem mais enxuta:

let soma = (a, b) => a + b;

console.log(soma(1, 3)); // 4
console.log(soma(32.4, -2)); // 30.4

Pode-se ler: “dados a e b, retorne a + b.

O resultado a expressão à direita será o conteúdo da função. Ao ser executada, caso a expressão retorne um valor, ele será o valor de retorno da função.

let quadrado = x => x ** 2; // só um parâmetro

let dado6Lados = () => Math.floor(Math.random() * 6) + 1; // nenhum parâmetro

let caraOuCoroa = () => { // expressão com múltiplas linhas
    if (Math.random() > 0.5) {
        return "cara";
    }
    return "coroa";
}

console.log(quadrado(2));
console.log(quadrado(3));

console.log(dado6Lados());
console.log(dado6Lados());
console.log(dado6Lados());

console.log(caraOuCoroa());
console.log(caraOuCoroa());
console.log(caraOuCoroa());

Sempre que possível, use arrow functions.

Processando arranjos com funções

As técnicas abaixo trazem um sabor de programação funcional para o JavaScript. Cada função opera sobre um arranjo, executando a sua tarefa enquanto percorre todos os seus elementos.

Executando uma função para cada um dos elementos

Dados um arranjo e uma função, podemos executar a função uma vez para cada elemento do arranjo usando forEach. Ele recebe uma função e a executa passando 3 argumentos, na sequência: o valor do elemento, o índice do elemento e a referência ao arranjo como um todo. Use-os conforme a sua necessidade.

let metade = x => x / 2;
let numeros = [2, 5, -17.9, 143, 1963.5];

numeros.forEach(alert); // Exibe todos, um a um
numeros.forEach(n => console.log(`A metade de ${n} é ${metade(n)}.`)); // metades
numeros.forEach((x, i) => {
    console.log(`\nÍndice #${i}:`);
    console.log(`Número = ${x}, Dobro = ${x*2}, Metade = ${metade(x)}.`)
}); // dobros

Usar forEach é sempre preferível a iterar arranjos.

Buscando e filtrando

As funções find, findIndex e filter funcionam de forma parecida com forEach, mas com alguns detalhes que permitem executar buscas e filtros.

let craques = [
    {nome: "Michael Jordan", esporte: "basquete"},
    {nome: "Pelé", esporte: "futebol"},
    {nome: "LeBron James", esporte: "basquete"},
];

let pele = craques.find(craque => craque.nome === "Pelé");
let nenhumTenista = !craques.find(craque => craque.esporte === "tênis");
let nenhumFutebol = !craques.find(craque => craque.esporte === "futebol");
let posicaoLBJ = craques.findIndex(craque => craque.nome === "LeBron James");
let craquesBasquete = craques.filter(craque => craque.esporte === "basquete");

console.log(pele);
console.log(nenhumTenista);
console.log(nenhumFutebol);
console.log(posicaoLBJ);
console.log(craquesBasquete);

Transformando um arranjo

A função map executa uma função para cada elemento e retorna um arranjo contendo os retornos, com índices equivalentes.

A função reduce processa todos os elementos da lista e traz como resultado somente um valor. Isso é muito útil em diversas situações, como calcular a soma e encontrar o maior item, por exemplo. Precisamos informar um identificador para o valor anterior e um para o valor atual.

Vejamos alguns exemplos:

let n = [2, 5, -3, 4];

let soma = n.reduce((ant, atual) => ant + atual);
let max  = n.reduce((ant, atual) => ant > atual ? ant : atual);

console.log(n);
console.log(soma); // 8
console.log(max);  // 5

Opcionalmente podemos também receber o índice atual, o arranjo como um todo e um valor inicial para o primeiro indicador, no formato arranjo.reduce((valorAnterior, valorAtual, indiceAtual, arranjoOrigem) => funcao, valorInicial).

Exemplo:

let credito = 100.00;
let compras = [
    {produto: "Playstation 5", preco: 5000.00, qtd: 1},
    {produto: "Calmante", preco: 60.00, qtd: 2, paraPresente: true}
];
let frete = 20.00;
let saldo = compras.reduce((parcial, compra) => 
    parcial - (compra.preco * compra.qtd)
, credito) - frete;

console.log(compras);
console.log(`Crédito: ${credito}`);
console.log(`Itens comprados: ${compras.length}`);
console.log(`Frete: ${frete}`);
console.log(`Saldo: ${saldo}`);

Métodos

Quando uma função é armazenada como uma propriedade de um objeto a chamamos de método. Métodos são elementos fundamentais da programação orientada a objetos.

Podemos declarar métodos de diversas formas diferentes:

let funcaoDeclaradaExternamente = () => { console.log("funcaoDeclaradaExternamente"); };

let objeto = {
    funcaoPreexistente: funcaoDeclaradaExternamente,
    funcaoDeclaradaExternamente,
    funcaoDeclaradaInternamente: function() {
        console.log("funcaoDeclaradaInternamente");
    },
    funcaoDeclaradaInternamenteComAtalho() {
        console.log("funcaoDeclaradaInternamenteComAtalho");
    },
    arrowFunction: () => { console.log("arrowFunction"); },
};

console.dir(objeto);
objeto.funcaoPreexistente();
objeto.funcaoDeclaradaExternamente();
objeto.funcaoDeclaradaInternamente();
objeto.funcaoDeclaradaInternamenteComAtalho();
objeto.arrowFunction();

Um cuidado que devemos ter é entender como funciona o acesso aos dados internos do objeto pelos métodos. Podemos ter acesso ao conteúdo interno do objeto chamador usando this. No exemplo abaixo, implementamos dois métodos, dizerOla usando a declaração de função, e dizerOlaComBug usando arrow functions. Perceba no segundo método não temos acesso ao objeto chamador porque arrow functions não possuem this.

let pessoa = {
    nome: "Ermogenes",
    dizerOla: function() {
        return `Olá, meu nome é ${this.nome}!`;
    },
    dizerOlaComBug: () => `Olá, meu nome é ${this.nome}!`,
};

console.log(pessoa.dizerOla());
console.log(pessoa.dizerOlaComBug());