← JavaScript: linguagem e diagnóstico de aplicações
02 / 10 · 50 MIN

Variáveis, funções e closures

Analisa variáveis capturadas, parâmetros e receivers para que callbacks posteriores observem o estado previsto.

Binding, objeto e efeito visível

const settings = { retries: 1 } cria um binding que não pode ser reatribuído. A propriedade settings.retries continua a poder mudar. Esta diferença é essencial ao passar dados para funções: o parâmetro recebe um valor e, quando esse valor referencia um objeto, a função e o chamador podem alcançar o mesmo objeto.

Uma escrita em p.count pode ser observada pelo chamador. Já p = { count: 0 } só muda o binding local do parâmetro. Essa reatribuição não substitui a variável do chamador nem desfaz escritas anteriores. Ao diagnosticar efeitos inesperados, desenha as referências antes e depois da chamada; o nome const, isoladamente, não demonstra que os dados são imutáveis.

O bloco interior pode ocultar um nome antes da inicialização

let e const têm âmbito de bloco. Se um bloco declara let mode, uma leitura de mode anterior à declaração nesse mesmo bloco não regressa automaticamente ao mode exterior. O nome resolve para o binding interior, ainda não inicializado. A leitura lança ReferenceError; até typeof mode está sujeito a essa temporal dead zone.

É diferente de um nome que não foi declarado em nenhum âmbito, para o qual typeof pode produzir "undefined". Ao rever uma falha que apareceu depois de inserir uma declaração local, procura primeiro que declaração o nome passou a identificar. Mover uma leitura ou mudar um nome pode alterar a resolução; evita explicar o caso apenas como uma variável que ainda não recebeu o valor final.

Parâmetros predefinidos e valores devolvidos

Em function quota(value = 4), o inicializador é usado quando o argumento é omitido ou undefined. Um null fornecido explicitamente mantém-se null; zero também não é substituído. O valor predefinido não declara um tipo obrigatório para o parâmetro. Os inicializadores são avaliados na chamada e o seu scope é exterior ao corpo da função.

Esta localização explica por que um let base declarado no corpo não fornece o base usado num inicializador de parâmetro. Na devolução, também importa a forma sintática: n => n * 2 devolve a expressão, enquanto n => { n * 2; } termina sem return e devolve undefined. Um callback pode concluir normalmente e ainda assim não entregar o valor que o chamador esperava.

Closures conservam acesso a bindings

Uma closure associa uma função ao ambiente lexical onde foi criada. Não se deve imaginar que guarda sempre uma fotografia de todos os valores. Se vários callbacks consultam um mesmo var i, leem o valor desse binding quando são chamados. Depois de um ciclo de 0 a 2 terminar, i é 3 e todos podem observar 3.

Num for com let i, a linguagem cria bindings por iteração. Isso permite associar cada callback ao índice correspondente. Também se pode criar uma função por chamada com um parâmetro próprio. Dois contadores criados por chamadas distintas de uma factory têm ambientes independentes, enquanto duas chamadas à mesma closure podem atualizar o mesmo contador retido.

Receiver: associação explícita e this lexical

Uma chamada reporter.read() fornece reporter como this ao método. Guardar apenas reporter.read e invocá-lo mais tarde como função isolada não conserva automaticamente esse receiver. Em código de módulo, essa chamada não fornece um objeto global alternativo: o método pode falhar ao ler this.count.

reporter.read.bind(reporter) cria um callback associado ao objeto. Continua a ler a propriedade atual na chamada; não clona count. Uma arrow, por seu lado, usa o this do contexto onde foi criada. call ou bind não substituem esse this lexical. Uma arrow criada no nível superior do módulo não ganha reporter como receiver só por ser guardada numa propriedade desse objeto.

Leitura guiada: identidade no momento da preparação

No exemplo buildReaders, cada iteração tem o seu i e lê id para um const local. Como o contrato restringe id a string, a alteração posterior records[0].id = "Z" não altera a string já lida. readers[0]() devolve um novo objeto com index 0 e id "A". Os parênteses em () => ({ index: i, id }) tornam o objeto a expressão devolvida.

Se, em vez da string, fosse guardado o objeto records[i], a propriedade desse objeto poderia mudar antes da chamada. Se o contrato exigisse o id atual, o exemplo precisaria de consultar o estado atual deliberadamente. Capturar mais cedo é uma escolha de comportamento, não uma melhoria automática em qualquer callback.

Diagnóstico por momento de leitura

Numa integração com ações adiadas, regista três momentos: criação do callback, alteração do estado e invocação. Depois identifica o binding ou receiver consultado em cada leitura. Esta sequência separa um índice partilhado, uma referência mutável e uma função chamada sem o objeto esperado. Acrescentar um temporizador não corrige por si só nenhum desses contratos.

Para fechar a análise, justifica se o resultado deve refletir o estado da preparação ou o estado atual. Escolhe depois a captura de um valor, um binding por iteração ou um receiver associado. Nas avaliações, os excertos definem a ordem e o ambiente; a tarefa é selecionar a interpretação ou correção compatível com esses factos.

function buildReaders(records) {
  const readers = [];
  for (let i = 0; i < records.length; i++) {
    const id = records[i].id;
    readers.push(() => ({ index: i, id }));
  }
  return readers;
}
const records = [{ id: "A" }, { id: "B" }];
const readers = buildReaders(records);
records[0].id = "Z";
const first = readers[0]();
NA PRÁTICA

Assume uma lista já validada com id do tipo string e nenhuma mutação durante buildReaders. O último valor é { index: 0, id: "A" }: foram capturados o índice da iteração e a string lida nessa altura. O exemplo não afirma que um objeto capturado seria copiado profundamente.

Armadilhas comuns

Confundir const com imutabilidade; esperar que var guarde um valor por iteração; esquecer return num bloco arrow; pensar que bind copia propriedades; usar this de um módulo como receiver de um objeto.

Tópicos relacionados: Objetos, identidade e limites das propriedades · Arrays, coleções e iteração · Promises, async e propagação de erros

Leva esta ideia contigo

Para cada callback, identifica que binding consulta, que objeto alcança e quando lê o respetivo valor.

Criar conta

Referência: Grammar and types · ECMAScript 2026 (17th edition); browser APIs identified separately