Dia 7 — Funções, escopo e closures
Declarar, chamar e passar dados
Declarar função: parâmetro, argumento e return
Uma função é um bloco com nome que guarda um trecho de código. A assinatura é function nome(parametro) { corpo }: o nome é como se chama, o parâmetro é o que a função recebe e o que se chama na invocação é o argumento.
function somar(a, b) { return a + b; } const total = somar(2, 3); // 5
return devolve um valor e encerra a função na hora. O que vem depois do return não roda. Sem return, a função devolve undefined — e é isso que console.log(minhaFuncao()) mostra quando a função não devolve nada.
Chamar função: o nome, a invocação e o que volta
Função declarada com nome fica disponível no escopo pelo próprio nome da função, e a sintaxe de chamar função é nome, parênteses e argumentos: cumprimentar('Bia'). Escrever só o nome não executa nada: cumprimentar é a referência à função, cumprimentar('Bia') é a execução. Nos manuais a mesma ação aparece com outras palavras — invocar, executar, disparar — e são sinônimos: abrir um quadro novo, casar cada argumento com o parâmetro da mesma posição e rodar o corpo até o fim ou até o return.
const f = somar; // o nome da função é um valor: dá para copiar f(2, 3); // mesmo resultado de somar(2, 3) somar.name; // 'somar' — o nome também é inspecionável
Quem não tem nome não pode ser invocado por nome: só existe no lugar onde foi guardada. É a função anônima, que volta na aula seguinte como função seta.
function cumprimentar(nome) { console.log('olá, ' + nome); } cumprimentar('Bia'); // imprime e nada é devolvido console.log(cumprimentar('Caio')); // imprime e devolve undefined
O console.log dentro da função é o caso mais direto de função sem retorno: o efeito acontece na tela e o valor que volta é undefined. Envolver a chamada em console.log faz a impressão aparecer duas vezes — uma do efeito da função, outra do valor devolvido.
Parâmetro, argumento e o que a função devolve
O parâmetro é a caixa; o argumento é o que foi colocado dentro dela. Duas funções com o mesmo nome de parâmetro não têm nada em comum: quem chama não sabe qual é o nome, só a posição.
function apresentar(nome, sobrenome) { return `${nome} ${sobrenome}`; } apresentar('Ana', 'Souza'); // 'Ana Souza' apresentar(); // 'undefined undefined'
return pode devolver um objeto pronto, não só um escalar: return { nome, ativo: true } entrega a estrutura inteira e quem recebe usa direto.
Efeito colateral
A função que não devolve nada e age no mundo é o tipo mais comum de código em produção: ela imprime, escreve no DOM, salva no banco. O que ela faz é efeito colateral, e vale a regra de manter o efeito em uma função só, para o resto do programa ser testável e a ordem das chamadas ficar explícita.
| Assinatura | Devolve | Serve para |
|---|---|---|
function soma(a, b) { } | o return | calcular |
function criarItem(x) { } | undefined | alterar algo que já existe |
function salvar(x) { } | Promise (quando async) | falar com servidor |
const soma = (a, b) => { } | o return | atalho, sem nome e sem this |
Função declarada com function é içada: pode ser chamada em uma linha antes da linha em que foi escrita. Função atribuída a const só existe depois da atribuição, porque é um valor como qualquer outro.
Parâmetros padrão e rest
function somar(a, b = 0) usa o padrão quando o argumento vem undefined — null e 0 não aciona o padrão, passam direto. Isso permite chamar com menos argumentos sem checar nada.
O rest parameter ...rest junta os argumentos que sobram num array, com const para não virar outro parâmetro:
function somarTudo(...numeros) { return numeros.reduce((total, n) => total + n, 0); } somarTudo(1, 2, 3); // 6
console.logé função, e a diferença aparece no retorno:console.log('a')devolveundefined, enquantoconsole.log('a' + 'b')imprime o texto. E atenção ao nome: dentro de uma função,consoleexiste; a variável que guarda o resultado doconsole.logprecisa de outro nome, ou o próprioconsolesome do escopo dali para fora.
Exemplo
'use strict'; // Exemplo: declaring, chamando e o que cada funcao devolve. function somar(a, b) { return a + b; } console.log('somar(2, 3) =', somar(2, 3)); // O parametro e a caixa; o argumento e o que foi colocado dentro dela. function apresentar(nome, sobrenome) { return `${nome} ${sobrenome}`; } console.log(apresentar('Ana', 'Souza')); console.log('sem argumento:', JSON.stringify(apresentar())); // return encerra a funcao: o que vem depois nao roda. function primeiroPar(numero) { if (numero % 2 === 0) { return 'par'; } return 'impar'; } console.log('primeiroPar(4):', primeiroPar(4), '| primeiroPar(7):', primeiroPar(7)); // Funcao sem return devolve undefined. E o que o console mostra. function cumprimentar(nome) { console.log(' ola, ' + nome); } console.log('cumprimentar devolve:', cumprimentar('Bia')); // Efeito colateral: a funcao age no mundo e nao devolve nada. const carrinho = []; function adicionar(produto) { carrinho.push(produto); console.log(' adicionou ' + produto + ' | itens: ' + carrinho.length); } adicionar('arroz'); adicionar('feijao'); console.log('carrinho final:', carrinho.join(', ')); // Parametro padrao: so o undefined dispara o padrao. null e 0 passam direto. function multiplicar(a, b = 2) { return a * b; } console.log('multiplicar(5):', multiplicar(5)); console.log('multiplicar(5, 3):', multiplicar(5, 3)); console.log('multiplicar(5, null):', multiplicar(5, null), '(null nao dispara o padrao)'); console.log('multiplicar(5, 0):', multiplicar(5, 0), '(0 nao dispara o padrao)'); // Rest parameter: o que sobrar vira array. function somarTudo(...numeros) { return numeros.reduce((total, n) => total + n, 0); } console.log('somarTudo(1, 2, 3):', somarTudo(1, 2, 3)); console.log('somarTudo() sem argumento:', somarTudo()); console.log('o rest e sempre um array:', Array.isArray(((...x) => x)(1, 2))); // A funcao declarada e icada: pode ser chamada antes da linha em que existe. console.log('chamada antes da declaracao:', triple(7)); function triple(n) { return n * 3; } // A atribuida a const so existe depois da atribuicao: e um valor. const metade = (n) => n / 2; console.log('metade(9):', metade(9)); // Return de estrutura: a funcao devolve o objeto inteiro. function criarItem(nome) { return { nome, ativo: true }; } const item = criarItem('teclado'); console.log('criarItem:', item.nome, '| ativo:', item.ativo); // Nome de parametro nao viaja para quem chama. function apresentar2(a, b) { return `${a} ${b}`; } console.log('o nome do parametro e local:', apresentar2('Ana', 'Souza'));
Saída real
somar(2, 3) = 5 Ana Souza sem argumento: "undefined undefined" primeiroPar(4): par | primeiroPar(7): impar ola, Bia cumprimentar devolve: undefined adicionou arroz | itens: 1 adicionou feijao | itens: 2 carrinho final: arroz, feijao multiplicar(5): 10 multiplicar(5, 3): 15 multiplicar(5, null): 0 (null nao dispara o padrao) multiplicar(5, 0): 0 (0 nao dispara o padrao) somarTudo(1, 2, 3): 6 somarTudo() sem argumento: 0 o rest e sempre um array: true chamada antes da declaracao: 21 metade(9): 4.5 criarItem: teclado | ativo: true o nome do parametro e local: Ana Souza
Arrow function, escopo e closures
Arrow function, escopo e closure
A arrow function é a forma curta: const soma = (a, b) => a + b. Sem function, sem nome, com o corpo em uma linha quando é uma expressão só. Com bloco, o return continua obrigatório.
O nome completo é função seta, e a seta é justamente o que dá o nome: =>. A outra metade do nome é o que a arrow tem menos — ela é sempre função anônima, sem nome, ou seja, ela só existe no lugar onde foi guardada.
const somar = (a, b) => a + b; const log = (msg) => { console.log(msg); }; const zero = () => 0; // parênteses obrigatórios mesmo vazio
A regra do parêntesis: com um parâmetro, msg => msg funciona. Com zero ou com dois, os parênteses voltam.
O parâmetro padrão é o valor que entra quando o argumento não vem: const saudar = (nome = 'visitante') => .... Ele só age quando o argumento é undefined — passar '' ou 0 usa o que foi passado, não o padrão.
Callback e função como valor
Função é valor. Isso significa que ela pode ser guardada em variável, passada como argumento e devolvida por outra função. Passar função para outra função executar é o que se chama função de callback, e é a base de map, filter, addEventListener e de todo o resto do curso.
const dobrar = (n) => n * 2; const aplicar = (valor, funcao) => funcao(valor); // funcao como parametro aplicar(5, dobrar); // 10
Arrow function não tem this próprio: o this dela é o do módulo onde foi escrita. Em código de hoje isso é o desejado quase sempre, e é a razão de a arrow ter substituído a função comum em callback.
Escopo: onde a variável pode ser usada
let e const nascem no bloco de chaves em que foram declarados e morrem no fim dele. Variável declarada fora de todo bloco fica no escopo global e é acessível de qualquer lugar — no navegador, isso é window.
O outro lado do escopo é o escopo de função: uma variável local declarada dentro de uma função só existe ali dentro, e nem a função que a chamou consegue enxergá-la. É a diferença entre global_ e local no exemplo.
const global_ = 'visivel de tudo'; function externa() { const local = 'so aqui dentro'; // variavel local: invisivel de fora function interna() { console.log(local); // funciona: a interna foi criada dentro da externa } }
interna não existe fora de externa, e local não existe fora dela. A ordem de declaração também importa: usar uma const antes da linha em que ela é declarada é ReferenceError, não undefined.
Closure: a função carrega o ambiente de onde nasceu
Closure não é palavra reservada; é o nome do que acontece quando uma função guardada em variável mantém acesso às variáveis do escopo onde foi criada, mesmo depois de a função que a criou ter terminado. O termo é fechamento, e o nome vem do fato de a função fechar sobre o ambiente onde nasceu.
function criarContador() { let contagem = 0; // mora dentro, ninguem de fora alcanca return function somar() { contagem++; return contagem; }; } const contador = criarContador(); contador(); // 1 contador(); // 2 contador(); // 3
O detalhe que importa: a variável contagem é preservada entre as chamadas. Não é que a variável de fora mudou — ela nunca foi exposta. Cada criarContador() cria um contagem novo e independente, então dois contadores não se misturam. É assim que se escreve estado privado sem classe.
const a = criarContador(); const b = criarContador(); a(); a(); // 1, 2 b(); // 1 — o estado de b é o seu, nao o de a
Memoizar: a closure que guarda o resultado
A mesma closure que preserva um contador também serve para guardar um resultado caro, em vez de recalculá-lo a cada chamada. É o que se chama memoizar, e o padrão é sempre o mesmo: uma chave, um mapa de resultados já calculados e a checagem no começo.
function memoizar(calcular) { const cache = new Map(); return (chave) => { if (cache.has(chave)) { return cache.get(chave); // ja foi calculado, devolve pronto } const resultado = calcular(chave); cache.set(chave, resultado); return resultado; }; } const caro = memoizar((n) => { /* calculo pesado aqui */ return n * 2; });
O rest parameter entra justamente aí: (...numeros) junta os argumentos que sobram num array, e o spread ... é o mesmo operador nos dois papéis — aqui ele junta os argumentos, no dia 8 ele despeja uma lista. É a diferença entre receber e enviar.
Esse é o mesmo mecanismo que sustenta addEventListener com contador de cliques, cache de resultado e memoização.
A armadilha clássica do laço é a mesma coisa pelo outro lado: com
var, todas as voltas doforcompartilham a mesma variável, e a closure criada em cada volta aponta para ela. As dez funções veilhes enxergam o mesmoi, já com o valor final. Comlet, cada volta cria um ambiente novo, e cada closure guarda o seu.
Exemplo
'use strict'; // Exemplo: arrow function, callback, escopo e closure. // O ponto central: a variavel interna e PRESERVADA entre as chamadas. // Nao e a variavel de fora que mudou — a de fora nunca foi exposta. function somar(a, b) { return a + b; } const somarSeta = (a, b) => a + b; const mensagem = (msg) => { console.log(' seta com bloco:', msg); }; const zero = () => 0; // parenteses obrigatorios mesmo vazio console.log('function:', somar(2, 3), '| arrow:', somarSeta(2, 3)); mensagem('ok'); console.log('zero():', zero()); // Funcao como valor: guardada em variavel e passada como argumento. const dobrar = (n) => n * 2; const aplicar = (valor, funcao) => funcao(valor); // funcao no parametro console.log('aplicar(5, dobrar):', aplicar(5, dobrar)); console.log('uma funcao e um valor:', typeof dobrar); // Callback: guardar os dois lados para o laco do dia 8. const numeros = [1, 2, 3]; console.log('map com callback:', numeros.map(dobrar).join(', ')); // --- ESCOPO ------------------------------------------------------------- const global_ = 'visivel de todo o codigo'; function externa() { const local = 'so existe dentro de externa'; console.log('dentro de externa:', local, '|', global_); function interna() { // A interna foi criada dentro da externa: enxerga o escopo dela. console.log('dentro de interna:', local); } interna(); } externa(); // O global e o mesmo objeto que a janela do navegador. console.log('globalThis existe:', typeof globalThis === 'object'); // --- CLOSURE ------------------------------------------------------------ function criarContador() { let contagem = 0; // estado privado, fora de alcance return function somar() { contagem++; // mexe no estado, nao numa variavel externa return contagem; }; } const contador = criarContador(); console.log('contador():', contador()); console.log('contador():', contador()); console.log('contador():', contador()); // A prova de que o valor foi PRESERVADO, e nao accomplishmento de fora: // a funcao devolve sempre o estado guardado, sem ninguem setar nada. console.log('o estado e da propria funcao:', contador()); // Cada chamada de criarContador cria um estado novo e independente. const a = criarContador(); const b = criarContador(); a(); a(); b(); console.log('a():', a(), '| b():', b(), '| os dois nao se misturam'); // A variavel de fora nao existe — o nome contagem nao existe neste arquivo. console.log('existe uma variavel global "contagem"?', typeof globalThis.contagem); // Funcao que devolve funcao: a forma de montar um filtro configuravel. function multiplicador(fator) { return (n) => n * fator; // o fator fica preso na funcao devolvida } const por10 = multiplicador(10); const por3 = multiplicador(3); console.log('por10(4):', por10(4), '| por3(4):', por3(4)); // --- A ARMADILHA DO LACO ------------------------------------------------- // Com var, as tres funcoes apontam para a MESMA variavel i, que ja terminou. const comVar = []; for (var i = 0; i < 3; i++) { comVar.push(() => i); } console.log('com var, as tres devolvem:', comVar.map((f) => f())); // Com let, cada volta cria um i novo, e cada closure guarda o seu. const comLet = []; for (let j = 0; j < 3; j++) { comLet.push(() => j); } console.log('com let, cada uma guardou o seu:', comLet.map((f) => f())); // A forma de escopo de bloco tambem funciona, e e explicita. const comBloco = []; for (let k = 0; k < 3; k++) { const copia = k; comBloco.push(() => copia); } console.log('copiando a variavel antes:', comBloco.map((f) => f()));
Saída real
function: 5 | arrow: 5 seta com bloco: ok zero(): 0 aplicar(5, dobrar): 10 uma funcao e um valor: function map com callback: 2, 4, 6 dentro de externa: so existe dentro de externa | visivel de todo o codigo dentro de interna: so existe dentro de externa globalThis existe: true contador(): 1 contador(): 2 contador(): 3 o estado e da propria funcao: 4 a(): 3 | b(): 2 | os dois nao se misturam existe uma variavel global "contagem"? undefined por10(4): 40 | por3(4): 12 com var, as tres devolvem: [ 3, 3, 3 ] com let, cada uma guardou o seu: [ 0, 1, 2 ] copiando a variavel antes: [ 0, 1, 2 ]