Dia 2 — Variáveis, let e const
let, const e o fim de var
let, const e o fim de var
Variável é um nome que aponta para um valor. Declarar é criar esse nome; atribuir é colocar um valor dentro dele. São duas frases separadas pelo sinal de igual.
let idade = 30; // declara e já atribui let cidade; // declara sem valor: vale undefined cidade = 'Recife'; // atribui depois
let é a forma usada hoje. var é a forma antiga, de 1995, e só aparece em código velho: ela respeita menos regras, e as regras soltas viram bug.
Nenhum dos três entrega um valor imutável no sentido forte da palavra. const é o que mais se aproxima, e mesmo assim trava só a ligação entre o nome e o valor: o conteúdo apontado continua podendo mudar, assunto da aula 2.
Identificador: o nome da variável
O nome pode ter letra, número, _ e $, e não pode começar por número. camelCase é a convenção do curso: primeira letra minúscula, sem espaço, palavras seguintes com maiúscula.
| Escreve | Vale | Por quê |
|---|---|---|
let idade = 30; | sim | nome simples |
let totalVendas = 0; | sim | camelCase, padrão do curso |
let _interno = 1; | sim | começa com _ |
let $elemento = 1; | sim | $ é permitido |
let 2total = 1; | não | começa com número |
let idade = 30 | não | falta o ponto e vírgula |
Palavra reservada não pode ser nome de variável: let, const, var, if, for, function, return, class, new, typeof. idade e Idade são nomes diferentes, porque a JavaScript diferencia maiúscula de minúscula.
Tipagem dinâmica
A variável não tem tipo. Quem tem tipo é o valor que está dentro dela, e esse tipo pode mudar a qualquer atribuição.
let idade = 30; // typeof idade devolve 'number' idade = 'trinta e um'; // typeof idade devolve 'string' idade = 31; // volta a ser 'number'
Não existe int, float ou string na declaração. Isso torna o código curto e tira do programador uma decisão que o erro só apareceria depois, longe do lugar do defeito.
Escopo de bloco: o que fica entre chaves
O escopo de bloco é o que fica entre { }. Um let declarado dentro de um bloco só existe dentro dele. Passou do bloco, o nome desapareceu.
const painel = 'título principal'; { const painel = 'título da seção'; // outro nome, mesmo texto console.log(painel); // 'título da seção' } console.log(painel); // 'título principal'
Os dois painel não se atrapalham porque estão em blocos diferentes. O let e o const sobem para dentro do bloco; nunca saem dele.
var é o contrário: declarada dentro de chaves, ela continua existindo depois do bloco. E ela sobe para o topo da função inteira antes de qualquer linha rodar, o chamado hoisting.
Usar um
letantes da linha em que ele é declarado dáReferenceError. Comvaro valor antigo eraundefinedem silêncio, e o erro aparecia três linhas depois, no lugar errado. Esse é o motivo da troca.
A redeclaração é um dos motivos do fim do var: declarar de novo, no mesmo bloco, um nome que já existe. Com let e com const isso nem chega a rodar — o navegador acusa o arquivo inteiro antes de executar a primeira linha. Com var era permitido, e quem redeclarava por engano só descobria depois.
var, let e const lado a lado
var | let | const | |
|---|---|---|---|
| Respeita o bloco | não | sim | sim |
| Redeclarar no mesmo bloco | permitido | erro | erro |
| Usar antes de declarar | undefined | erro | erro |
| Reatribuir | sim | sim | erro |
| Aparece em código novo | não | sim | sim |
var falhou nos quatro primeiros itens da tabela, e é por isso que saiu de moda. Código novo usa const e let, sem exceção.
Exemplo
'use strict'; // ============================================================ // AULA 1 - let, const e o fim de var // // `let` e `const` declaram a variável; `var` é a forma antiga, que // só aparece em código velho. Este exemplo declara com as três e // mostra o que cada uma faz de diferente — inclusive os erros, // capturados para o script não morrer no meio. // // Os `console.error` deste arquivo saem no fim da saída do terminal, // separados do texto normal. A linha de `console.log` que vem logo // depois de cada um é a que aparece na página. // ============================================================ // Declarar é uma frase; atribuir é outra. `let` cria a ligação // entre o nome e o lugar onde o valor mora, `=` coloca o valor. let idade = 30; let cidade; cidade = 'Recife'; console.log('--- declarar e atribuir ---'); console.log('idade:', idade); console.log('cidade:', cidade, '(declarada e atribuída em linhas separadas)'); // A tipagem é dinâmica: a variável não tem tipo fixo, o VALOR tem. // Trocar o tipo é permitido e não deixa rastro. idade = 'trinta e um'; console.log(''); console.log('--- tipagem dinâmica ---'); console.log('idade agora é texto:', idade, typeof idade); idade = 31; console.log('idade voltou a ser número:', idade, typeof idade); // O escopo de bloco é o que fica entre chaves. Um `let` declarado // dentro do bloco só existe dentro dele, e o mesmo nome pode se // repetir depois do bloco. const painel = 'título principal'; { const painel = 'título da seção'; console.log(''); console.log('--- escopo de bloco ---'); console.log('dentro do bloco:', painel); } console.log('fora do bloco:', painel, '(o nome de dentro não vazou para fora)'); // Redeclarar o mesmo `let` no mesmo bloco é erro de sintaxe: o // navegador acusa o arquivo inteiro antes de executar a primeira // linha. O `eval` existe só para este exemplo poder mostrar a // mensagem do erro e seguir em frente. console.log(''); console.log('--- o que dá erro, e de propósito ---'); console.log('1) redeclarar o mesmo let no mesmo bloco'); try { eval('let idade = 40; let idade = 50;'); } catch (erro) { console.error(erro.constructor.name + ': ' + erro.message); console.log(' erro:', erro.constructor.name, '-', erro.message); } // Usar a variável antes da linha em que ela é declarada também é // erro. Com `var` o valor antigo era `undefined` em silêncio; com // `let` o acesso é barrado e o erro diz o que aconteceu. console.log('2) usar a let antes da linha que a declara'); try { console.log(media); let media = 7.5; } catch (erro) { console.error(erro.constructor.name + ': ' + erro.message); console.log(' erro:', erro.constructor.name, '-', erro.message); } // `var` não respeita o bloco: declarada dentro de chaves, ela // continua existindo depois. E ela sobe para o topo da função // inteira, o chamado hoisting. { var contador = 1; console.log(''); console.log('--- var ignora o bloco ---'); console.log('declarado dentro do bloco:', contador); } console.log('declarado com var, lido fora:', contador); // O `typeof` é a única consulta que não quebra em variável que não // existe: é assim que o código descobre o que já foi criado. Ele // também não cria a variável, então ler o nome depois continua erro. console.log(''); console.log('--- typeof não quebra em nome que não existe ---'); console.log('typeof naoDeclarada:', typeof naoDeclarada); console.log('typeof contador:', typeof contador, '(o que existe, devolve o tipo)'); console.log('3) ler o nome que não foi declarado'); try { console.log(naoDeclarada); } catch (erro) { console.error(erro.constructor.name + ': ' + erro.message); console.log(' erro:', erro.constructor.name, '-', erro.message); }
Saída real
--- declarar e atribuir --- idade: 30 cidade: Recife (declarada e atribuída em linhas separadas) --- tipagem dinâmica --- idade agora é texto: trinta e um string idade voltou a ser número: 31 number --- escopo de bloco --- dentro do bloco: título da seção fora do bloco: título principal (o nome de dentro não vazou para fora) --- o que dá erro, e de propósito --- 1) redeclarar o mesmo let no mesmo bloco erro: SyntaxError - Identifier 'idade' has already been declared 2) usar a let antes da linha que a declara erro: ReferenceError - Cannot access 'media' before initialization --- var ignora o bloco --- declarado dentro do bloco: 1 declarado com var, lido fora: 1 --- typeof não quebra em nome que não existe --- typeof naoDeclarada: undefined typeof contador: number (o que existe, devolve o tipo) 3) ler o nome que não foi declarado erro: ReferenceError - naoDeclarada is not defined
const, escopo de bloco e o que não pode mudar
const, escopo de bloco e o que não pode mudar
const declara uma variável cujo nome não pode mais ser ligado a outro valor. O valor apontado continua podendo mudar — essa é a distinção que separa esta aula da anterior.
const configuracao = { tema: 'escuro' }; configuracao.tema = 'claro'; // permitido: mudou o conteúdo configuracao = { tema: 'escuro' }; // TypeError: mudou a ligação
O const trava o nome, não o conteúdo. A analogia que resolve: o nome é a etiqueta colada na caixa, e o que está dentro da caixa continua à disposição.
const com objeto e com array
O erro mais comum do curso nasce aqui. const objeto = {} não diz que o objeto é imutável: diz que a variável objeto não vai apontar para outro objeto.
const lista = ['Ana', 'Bia']; lista.push('Cau'); // o array cresce: permitido lista.length; // 3 lista = ['Ana']; // TypeError: agora sim, proibido
Com objeto, a propriedade pode ser criada do nada, alterada e apagada. Com array, além disso, dá para acrescentar, remover e reordenar. Tudo isso continua funcionando sob um const, porque o que o const proíbe é a reatribuição do nome, não a mudança do conteúdo mutável apontado.
Object.freeze: o degrau seguinte
Object.freeze congela o conteúdo, não só a ligação. Depois do congelamento, escrever na propriedade é TypeError.
| Função | Assinatura | Exemplo |
|---|---|---|
Object.freeze | Object.freeze<T>(o: T): Readonly<T> | Object.freeze({ versao: 1 }) |
Object.isFrozen | Object.isFrozen(o: object): boolean | Object.isFrozen(config) |
O congelamento é raso: congela as propriedades do primeiro nível, e um objeto dentro do objeto continua mutável. Ele também não sobrevive a JSON.parse, que cria um objeto novo.
const por omissão
A regra prática do curso é uma só: const por omissão, let só quando o valor realmente muda. Isso dá um padrão de leitura — quem lê o let já sabe onde procurar a mudança.
A comparação const versus let tem uma diferença e uma semelhança. A semelhança: os dois respeitam o bloco e os dois acusam uso antes da declaração, igual ao const da aula anterior. A diferença: o let pode receber outro valor na linha de baixo, e o const não.
A pergunta que decide é curta, e é a mesma que o aluno faz quando digita quando usar const: esse nome recebe outro valor em algum momento do código? Se não recebe, é const; se recebe, é let. A segunda parte da regra é preferir const — escrever let exige um motivo, escrever const não.
| Situação | Declare com |
|---|---|
| configuração, etiqueta, tabela de verdade | const |
| função | const |
item do for...of | const |
| contador, accumulating resultado | let |
| texto que a pessoa digita no campo | let |
const com função não é um caso especial: função é valor como outro, e a seta vai atrás de um nome declarado com const.
const somar = (a, b) => a + b; somar(2, 3); // 5
A seta => é a arrow function, assunto da aula 7. Aqui importa só o const na frente dela.
const em laço
No for...of, const é a escolha certa e não é preciosismo: cada volta do laço cria um nome novo, então o código do corpo não tem como estragar o valor da volta seguinte.
for (const nome of ['Ana', 'Bia', 'Cau']) { console.log(nome); }
Com let, o mesmo nome é reatribuído a cada volta. Com var, é a mesma variável desde o começo da função, e um setTimeout disparado no laço acabou mostrando sempre o último valor.
constnão significa "fixo" no sentido de "igual para sempre". Significa "esta referência não muda". Escreverconst total = 0; total++;dáTypeError— e essa é a intenção: o código estava dizendo que o total nunca mudava, e o++está dizendo o contrário.
Exemplo
'use strict'; // ============================================================ // AULA 2 - const, escopo de bloco e o que não pode mudar // // `const` proíbe a reatribuição: o nome aponta para o mesmo valor // para sempre. O que o valor aponta continua podendo mudar, e é // aí que nasce a confusão mais comum do curso. // // Os `console.error` deste arquivo saem no fim da saída do terminal, // separados do texto normal. A linha de `console.log` que vem logo // depois de cada um é a que aparece na página. // ============================================================ // `const` impede trocar a ligação do nome, não impede mexer no // conteúdo apontado. const configuracao = { tema: 'escuro', idioma: 'pt-BR' }; console.log('--- const com objeto ---'); console.log('antes:', configuracao); configuracao.tema = 'claro'; configuracao.aba = 'relatório'; console.log('depois de mexer nas propriedades:', configuracao); const lista = ['Ana', 'Bia']; console.log(''); console.log('--- const com array ---'); lista.push('Cau'); console.log('depois de push:', lista, '(o array cresce, a ligação não muda)'); // Reatribuir a própria variável é o que o `const` proíbe. O erro // é TypeError e sai na hora, sem computador errado em silêncio. console.log(''); console.log('--- o que dá erro, e de propósito ---'); console.log('1) trocar a variável inteira'); try { configuracao = { tema: 'claro' }; } catch (erro) { console.error(erro.constructor.name + ': ' + erro.message); console.log(' erro:', erro.constructor.name, '-', erro.message); } console.log('2) trocar um array inteiro por outro'); try { lista = []; } catch (erro) { console.error(erro.constructor.name + ': ' + erro.message); console.log(' erro:', erro.constructor.name, '-', erro.message); } // Object.freeze é o degrau seguinte: congela o CONTEÚDO, não só a // ligação. Depois do freeze, escrever na propriedade é erro. const congelado = Object.freeze({ versao: 1 }); console.log(''); console.log('--- Object.freeze ---'); console.log('antes da tentativa, versão:', congelado.versao); console.log('3) mexer no conteúdo de um objeto congelado'); try { congelado.versao = 2; } catch (erro) { console.error(erro.constructor.name + ': ' + erro.message); console.log(' erro:', erro.constructor.name, '-', erro.message); } console.log('depois da tentativa, versão:', congelado.versao, '(continua 1)'); // A regra prática do curso: `const` por omissão, `let` só quando o // valor realmente muda. Uma função é um valor como outro, e pode // ser `const`. const somar = (a, b) => a + b; console.log(''); console.log('--- const por omissão ---'); console.log('somar(2, 3) =', somar(2, 3)); console.log('const vale para número, texto, objeto, array e função'); // Em laço, `const` é o lugar onde ele brilha: cada volta cria um // nome novo e o valor nunca é reatribuído, então não há como o // código do corpo estragar o valor da volta seguinte. console.log(''); console.log('--- const em laço ---'); for (const nome of ['Ana', 'Bia', 'Cau']) { console.log(' processando', nome); } // A diferença que resume a aula inteira: `const` trava o nome, // `let` destrava. Os dois respeitam o bloco; `var` não respeita // nenhum, e por isso saiu de moda. console.log(''); console.log('--- onde cada um vale ---'); console.log('const: valor fixo (configuração, função, etiqueta, item do laço)'); console.log('let: valor que muda (contador, texto do campo, seleção)'); console.log('var: só em código antigo'); console.log(''); { const unico = 'só existe aqui dentro'; console.log('dentro do bloco, único =', unico); } console.log('fora do bloco, o nome já não existe mais');
Saída real
--- const com objeto ---
antes: { tema: 'escuro', idioma: 'pt-BR' }
depois de mexer nas propriedades: { tema: 'claro', idioma: 'pt-BR', aba: 'relatório' }
--- const com array ---
depois de push: [ 'Ana', 'Bia', 'Cau' ] (o array cresce, a ligação não muda)
--- o que dá erro, e de propósito ---
1) trocar a variável inteira
erro: TypeError - Assignment to constant variable.
2) trocar um array inteiro por outro
erro: TypeError - Assignment to constant variable.
--- Object.freeze ---
antes da tentativa, versão: 1
3) mexer no conteúdo de um objeto congelado
erro: TypeError - Cannot assign to read only property 'versao' of object '#<Object>'
depois da tentativa, versão: 1 (continua 1)
--- const por omissão ---
somar(2, 3) = 5
const vale para número, texto, objeto, array e função
--- const em laço ---
processando Ana
processando Bia
processando Cau
--- onde cada um vale ---
const: valor fixo (configuração, função, etiqueta, item do laço)
let: valor que muda (contador, texto do campo, seleção)
var: só em código antigo
dentro do bloco, único = só existe aqui dentro
fora do bloco, o nome já não existe mais