Dia 7 — Funções
Declarar e chamar uma função
Declarar e chamar uma função
A pergunta "como declarar função" tem uma resposta: function mais o nome, e a
função só executa quando alguém
chamar por esse nome. Ela existe para nomear uma tarefa, receber dados,
processar e devolver resultado.
function somar(a, b) { return a + b; } console.log(somar(2, 3)); // 5
O que está entre parênteses na declaração são os parâmetros; o que está entre
parênteses na chamada são os argumentos. No corpo, os parâmetros são nomes
que valem o que foi passado. O return é o retorno: ele encerra a função e
entrega o valor.
Sem return, a função devolve undefined. Esse detalhe importa quando o
resultado é usado em conta:
function semRetorno() { } console.log(semRetorno() + 1); // NaN
O return também encerra a função na hora. O que está depois dele não roda, e é
por isso que o retorno antecipado funciona como saída única.
As duas linhas do exemplo que tratam disso são `sem return: undefined |
undefined + 1 = NaN e retorno antecipado: vazio | tem itens`. A primeira
confirma a regra do undefined com a conta ao lado: sem return, o valor é
undefined, e undefined + 1 é NaN. A segunda mostra que classifica(0) saiu
pela linha do if e classifica(3) pela última — e o return 'tem itens' só é
alcançado quando o if não pegou.
A função seta, que é a forma do React
A função seta ou arrow function é a forma curta, com => no lugar de
function:
const somar = (a, b) => a + b;
Quando o corpo é uma única expressão, as chaves e o return podem ficar de fora.
Quando o corpo tem mais de um comando, as chaves voltam e o return precisa ser
escrito:
const descrever = (total) => { const plural = total === 1 ? '' : 's'; return `${total} tarefa${plural}`; };
Em React Native, quase todo código é arrow function: componente é função,
callback de botão é função, função passada para map é função. A **função
anônima** é a que não tem nome — é o que a seta produz quando você não escreve
const nome =.
O exemplo mostra os dois corpos da seta lado a lado. dobrar tem uma expressão
só e sai em seta curta: 42, sem chaves e sem return — o próprio corpo é o
valor devolvido. descrever tem dois comandos e a linha sai
seta com bloco: 1 tarefa | 5 tarefas: o plural foi calculado numa linha e
devolvido noutra, o que obrigou as chaves e o return explícito. São as duas
formas do parágrafo, com o resultado de cada uma.
A função anônima aparece na linha do forEach, que imprime `anonima:
estudar e anonima: treinar. Ali não existe const nome =`: a seta foi passada
direto como argumento. É por isso que o nome do exemplo importa — o forEach
chama a mesma função duas vezes, uma por item, e cada chamada é anônima e igual.
A IIFE: chamar na hora da declaração
Imediatamente executada é o nome do padrão em que a função é declarada e
chamada na mesma linha, entre parênteses:
(function () { console.log('roda uma vez, ao carregar o arquivo'); })();
O uso no app é inicializar configuração, registrar log e montar estado inicial —
tudo o que precisa acontecer uma vez, quando o módulo carrega.
O exemplo mostra a IIFE escrevendo IIFE: rodou uma vez, e o nome da linha já é
o resumo: uma vez, no carregamento. O (() => { ... })() é a mesma coisa
escrita com seta, e é a forma que aparece nos arquivos de configuração.
O detalhe é o segundo parêntese. Os parênteses externos primeiro agrupam a
função — sem eles, function () {} na linha seguinte seria lido como declaração
e não como valor — e o segundo parêntese é a chamada. (function () {}) sem
o () final define a função e não a executa.
Hoisting: a função inteira sobe
A declaração de função é içada (hoisting): o JavaScript sobe a declaração
inteira para o topo do arquivo antes de executar qualquer linha. Chamar uma função
antes da linha em que ela foi escrita funciona:
console.log(dizerOi()); // funciona function dizerOi() { return 'oi'; }
let e const não sobem. Usá-los antes da declaração é erro:
ReferenceError: Cannot access 'x' before initialization. E var sobe, mas
sobe sem valor — ler antes da atribuição devolve undefined, não o valor.
A regra prática: função declarada com function pode ser chamada em qualquer
ponto do arquivo; arrow function precisa estar declarada antes de ser usada.
O exemplo põe os três comportamentos em linhas seguidas, e é a ordem das linhas
que prova a regra. hoisting de function: oi sai antes da linha que declara
dizerOi. Depois vem `let nao sobe: ReferenceError - Cannot access
'antesDaDeclaracao' before initialization, e o erro precisa de try/catch`
porque derrubaria o arquivo inteiro — é a diferença entre um erro de sintaxe,
que nem chega a executar, e um de execução, que para na linha.
A terceira linha é o caso intermediário: var sobe sem valor: undefined. O var
sobe, como o function, mas sobe vazio: a linha var valorVar = 10 continua
abaixo do return, então a função devolveu undefined e não 10. As três
linhas juntas são a resposta completa: function sobe inteiro, var sobe vazio,
let e const não sobem.
E o detalhe de .name está na penúltima linha: `nome: somar | nome da seta:
dobrar. Uma seta declarada com const dobrar = ... tem o nome do const`, mesmo
escrita como valor anônimo — é assim que o map imprime o nome de cada função
quando algo dá erro dentro dela.
A última linha do exemplo é a que abre a aula seguinte:
funcao que recebe funcao: [ 'ESTUDAR', 'TREINAR' ]. aplicar recebe a lista e
a função, e entrega o map dela. Passar função como argumento é o que permite o
map, o filter e o useState — e o detalhe é que a função passada foi uma
seta anônima que só existia naquele argumento.
Exemplo
// Declarar e chamar funcao: a forma com `function` e a funcao seta, que e // a forma que o React Native usa em quase todo lugar. function somar(a, b) { return a + b; } console.log('funcao declarada:', somar(2, 3)); // parametro e o nome na declaracao; argumento e o que foi passado function saudar(nome) { return `ola, ${nome}`; } console.log('saudar:', saudar('Ana')); // sem return, a funcao devolve undefined function semRetorno() { } console.log('sem return:', semRetorno(), '| undefined + 1 =', semRetorno() + 1); // o return encerra a funcao: o que esta depois nao roda function Classifica(total) { if (total === 0) return 'vazio'; return 'tem itens'; // o return anterior ja saiu } console.log('retorno antecipado:', Classifica(0), '|', Classifica(3)); /* funcao seta: corpo de uma expressao nao precisa de chaves nem return */ const dobrar = (n) => n * 2; console.log('seta curta:', dobrar(21)); // com mais de um comando, as chases voltam e o return e escrito const descrever = (total) => { const plural = total === 1 ? '' : 's'; return `${total} tarefa${plural}`; }; console.log('seta com bloco:', descrever(1), '|', descrever(5)); // funcao anonima: o que a seta produz quando nao ha const nome = const tarefa = ['estudar', 'treinar']; tarefa.forEach((item) => console.log('anonima:', item)); /* IIFE: chamada imediata, roda uma vez ao carregar o arquivo */ (function () { console.log('IIFE: rodou uma vez'); })(); /* hoisting: function sobe inteiro antes de executar */ console.log('hoisting de function:', dizerOi()); function dizerOi() { return 'oi'; } /* let nao sobe: usar antes da declaracao e ReferenceError */ try { console.log(antesDaDeclaracao); let antesDaDeclaracao = 1; } catch (erro) { console.error(erro.constructor.name + ': ' + erro.message); console.log('let nao sobe:', erro.constructor.name, '-', erro.message); } /* var sobe sem valor */ console.log('var sobe sem valor:', (function () { return valorVar; var valorVar = 10; })()); /* nome da funcao, util no log */ console.log('nome:', somar.name, '| nome da seta:', dobrar.name); /* funcao que recebe funcao: o padrao do map */ function aplicar(valores, transformar) { return valores.map(transformar); } console.log('funcao que recebe funcao:', aplicar(tarefa, (t) => t.toUpperCase()));
Saída real
funcao declarada: 5 saudar: ola, Ana sem return: undefined | undefined + 1 = NaN retorno antecipado: vazio | tem itens seta curta: 42 seta com bloco: 1 tarefa | 5 tarefas anonima: estudar anonima: treinar IIFE: rodou uma vez hoisting de function: oi let nao sobe: ReferenceError - Cannot access 'antesDaDeclaracao' before initialization var sobe sem valor: undefined nome: somar | nome da seta: dobrar funcao que recebe funcao: [ 'ESTUDAR', 'TREINAR' ]
Argumentos, escopo e funções anônimas
Argumentos, escopo e funções anônimas
O que acontece com o valor que entra na função depende do tipo dele. **Argumento
por valor** significa que a função recebe uma cópia: mudar o parâmetro não muda o
argumento. Os parâmetros são os nomes na declaração; os argumentos são os valores
na chamada. Argumento por referência significa que a função recebe o objeto
mesmo: mudar a propriedade muda o objeto de quem chamou.
function renomear(usuario, nome) { usuario.nome = nome; // mudou o objeto de quem chamou } const ana = { nome: 'Ana' }; renomear(ana, 'Bia'); console.log(ana.nome); // 'Bia'
Número, texto, booleano, null e undefined são copiados. Objeto e array são
passados como referência — é por isso que push dentro de uma função altera o
array de fora, e é por isso que uma função que precisa alterar sem mexer no
original precisa devolver um array novo.
O exemplo separa os dois lados em duas linhas. por valor: 101 vem da função que
recebe 1 e 3, escreve 99 em a e devolve 99 + 3: o a original da
chamada ficou valendo 1, e ninguém consegue ver isso pelo resultado. `por
referencia: Bia | mesmo objeto: true vem da função que escreve no usuario` e
devolve o próprio usuario; o ana.nome saiu 'Bia' e a comparação
ana === resultado saiu true, que é a prova de que não existiu cópia.
O true dessa segunda linha é o detalhe que fecha a regra. Uma cópia daria
false — e o que o console.log mostra é que a escrita dentro da função
alcançou o objeto de quem chamou.
Parâmetro padrão e rest
Parâmetro padrão é o valor usado quando o argumento não vem:
function saudar(nome, prefixo = 'ola') { return `${prefixo}, ${nome}`; }
Rest (...) junta o que sobra dos argumentos num array:
function contar(...numeros) { return numeros.length; } contar(1, 2, 3); // 3
O ... no lugar do nome é o spread, e o mesmo operador em outro lugar faz o
oposto: espalha o conteúdo. A diferença é a posição.
O exemplo chama contar(1, 2, 3) duas vezes na mesma linha e o console responde
rest: 3 | o array: 3. Os dois 3 são o mesmo número: o length e o próprio
array com os três itens — o rest sempre chega como array, mesmo com um
argumento só, porque é uma lista de argumentos que sobraram.
A assinatura é o que define o rest: ele precisa ser o último parâmetro,
porque tudo depois dele seria argumento sem nome. Com (primeiro, ...resto), o
primeiro sai do array e resto fica com o que sobrou — e é assim que se pega
o primeiro item de uma lista de argumentos sem shift.
Escopo: o nome de dentro não existe fora
A variável local é a que fica dentro da função. Função cria escopo próprio. Nome declarado dentro só existe dentro:
const global = 'visivel'; function f() { const local = 'so aqui dentro'; }
Ler local fora da função é ReferenceError, e o mesmo acontece com const e
let. A variável global é o nome declarado no topo do arquivo, fora de
qualquer função — ela é alcançável de todo lugar, e é por isso que o código do
app evita declarar tudo global: com nome global, dois componentes que usam
total estão usando o mesmo total.
O exemplo mostra os dois lados em duas linhas. `escopo: visivel em todo o
arquivo / so dentro da funcao` vem da função que usa os dois nomes: o global foi
lido de dentro, e o local foi lido no mesmo return. A linha seguinte tenta ler
o local de fora e o console responde `local existe fora? ReferenceError:
local is not defined`.
O ReferenceError é a resposta que fecha o parágrafo: não é undefined, é erro.
E o nome global visível de dentro não é o mesmo objeto que o local — são dois
nomes independentes, e a função só enxerga os dois porque foi escrita no mesmo
arquivo.
Closure: a função guarda o escopo
Closure é a função junto com o acesso às variáveis do escopo onde foi criada.
A função interna continua enxergando n depois que a externa terminou:
function criarContador() { let n = 0; return () => { n = n + 1; return n; }; }
Cada chamada de criarContador cria um n novo e uma closure nova. É o que faz
o contador guardar o valor entre chamadas, e é o mesmo mecanismo que sustenta o
estado de um componente.
A linha do exemplo é closure: 1 2 | nova instancia: 1, e ela traz as duas
provas numa frase. As duas primeiras chamadas do mesmo contador devolvem 1 e
2 — o n sobreviveu entre elas, apesar de criarContador já ter terminado. A
terceira é uma chamada nova de criarContador(), e devolve 1 outra vez.
Esse 1 repetido é o ponto: se o n fosse global, a terceira chamada
continuaria em 2. Cada chamada cria uma caixa nova, e é a caixa que a
closure carrega junto. É exatamente esse mecanismo que o useState usa, com a
diferença de que o estado é guardado pelo runtime e não por um let do
arquivo.
Função anônima e os atalhos
A função anônima é a seta sem nome, e é a forma de quase todo callback:
aoTerminar(tarefas, (total) => console.log(total));
Os curto circuits — curto circuito é o nome técnico — dos && e || dentro de função substituem o if de uma
linha. && devolve a esquerda quando ela é falsy — é o atalho que evita acessar
propriedade de quem não existe. || devolve a esquerda quando ela é truthy, e é
a forma curta do valor padrão quando o valor existe:
function nomeSeguro(usuario) { return (usuario && usuario.nome) || 'sem nome'; }
O detalhe do this vale registro: a função seta não tem this próprio. Ela
não sabe de qual objeto foi chamada, e por isso não funciona como método de
objeto. Quando o método precisa de this, é function que se usa.
O exemplo chama a função anônima como argumento e o console responde `funcao
anonima chamada: 2 — o 2 é o length da lista que aoTerminar` recebeu, e
não uma variável de fora. É a função anônima fazendo o serviço completo, com o
dado entregue pelo dono da função.
Os atalhos aparecem na linha seguinte, com os dois lados de uma vez:
&& e ||: Ana | sem nome. Com { nome: 'Ana' }, o && devolveu o nome e o ||
aceitou; com null, o && cortou antes do .nome e o || forneceu 'sem nome'.
E a linha do this é seta nao tem this proprio: sem this. A seta está como
propriedade do objeto relogio, tem horas: 10 ao lado, e mesmo assim não
consultou nada disso — devolveu o texto fixo. Uma function no mesmo lugar
receberia this valendo relogio e leria this.horas.
A última linha é o padrão do onPress: aoClicar('marcar como feita')() sai
executou: marcar como feita. A função devolve outra função, e essa segunda
chamada executa a ação com o dado que a primeira recebeu. É assim que o botão do
app guarda a ação sem executar nada no momento de renderizar.
Exemplo
// Argumentos, escopo e funcoes anonimas: o que acontece com o valor que // entra e o que fica depois da chamada. /* argumento por valor: o numero que entra e uma copia */ function somar(a, b) { a = 99; return a + b; } console.log('por valor:', somar(1, 2)); /* argumento por referencia: o objeto que entra e o mesmo objeto */ function renomear(usuario, nome) { usuario.nome = nome; return usuario; } const ana = { nome: 'Ana' }; const resultado = renomear(ana, 'Bia'); console.log('por referencia:', ana.nome, '| mesmo objeto:', ana === resultado); /* parametro padrao: quando o argumento nao vem, o padrao entra */ function saudar(nome, prefixo = 'ola') { return `${prefixo}, ${nome}`; } console.log('padrao usado:', saudar('Ana'), '| padrao trocado:', saudar('Ana', 'bom dia')); /* rest: o que sobra dos argumentos vira array */ function contar(...numeros) { return numeros.length; } console.log('rest:', contar(1, 2, 3), '| o array:', contar(1, 2, 3)); /* escopo: o nome de dentro nao existe fora */ const global = 'visivel em todo o arquivo'; function usarEscopo() { const local = 'so dentro da funcao'; return `${global} / ${local}`; } console.log('escopo:', usarEscopo()); console.log('local existe fora?', (() => { try { return local; } catch (e) { return e.constructor.name + ': ' + e.message; } })()); /* closure: a funcao guarda acesso ao escopo onde foi criada */ function criarContador() { let n = 0; return () => { n = n + 1; return n; }; } const contador = criarContador(); console.log('closure:', contador(), contador(), '| nova instancia:', criarContador()()); /* funcao anonima recebida como parametro */ function aoTerminar(tarefas, quando) { quando(tarefas.length); } aoTerminar(['estudar', 'treinar'], (total) => console.log('funcao anonima chamada:', total)); /* atalhos && e || dentro da funcao */ function nomeSeguro(usuario) { return usuario && usuario.nome || 'sem nome'; } console.log('&& e ||:', nomeSeguro({ nome: 'Ana' }), '|', nomeSeguro(null)); /* o this dentro da funcao seta */ const relogio = { horas: 10, descrever: () => 'sem this' }; console.log('seta nao tem this proprio:', relogio.descrever()); /* funcao que devolve funcao, o padrao do onPress */ function aoClicar(acao) { return () => console.log('executou:', acao); } aoClicar('marcar como feita')();
Saída real
por valor: 101 por referencia: Bia | mesmo objeto: true padrao usado: ola, Ana | padrao trocado: bom dia, Ana rest: 3 | o array: 3 escopo: visivel em todo o arquivo / so dentro da funcao local existe fora? ReferenceError: local is not defined closure: 1 2 | nova instancia: 1 funcao anonima chamada: 2 && e ||: Ana | sem nome seta nao tem this proprio: sem this executou: marcar como feita