Variáveis: var, let e const
Nesta aula, você aprenderá as diferenças entre var, let e const no JavaScript, incluindo declaração, escopo, hoisting e boas práticas de uso. Com exemplos práticos, você entenderá quando cada uma é mais adequada para escrever código moderno e seguro.
Em JavaScript, variáveis são utilizadas para armazenar dados que podem ser manipulados durante a execução do programa. Desde o ES6 (ECMAScript 2015), temos três formas de declarar variáveis: var, let e const. Cada uma possui características distintas de escopo, hoisting e possibilidade de reatribuição. Compreender essas diferenças é fundamental para evitar bugs e escrever código claro e previsível.
Nesta aula, vamos explorar cada um desses tipos de declaração, analisar seu comportamento em diferentes contextos e definir as melhores práticas para seu uso no dia a dia.
Declaração e atribuição
A declaração de uma variável cria um identificador no escopo atual. A atribuição armazena um valor nesse identificador. Com var, let e const, a sintaxe é semelhante, mas há diferenças importantes.
var permite redeclarar a mesma variável no mesmo escopo sem erro, o que pode causar confusão. Já let e const não permitem redeclaração no mesmo escopo. const exige que a variável seja inicializada na declaração e não pode ser reatribuída. Exemplos:
var nome = "João";
var nome = "Maria"; // permitido, sobrescreve
let idade = 30;
// let idade = 25; // Erro: Identificador 'idade' já foi declarado
const PI = 3.1415;
// PI = 3; // Erro: Atribuição a variável constante.
// const X; // Erro: Falta inicializador na declaração const
É importante notar que const não torna o valor imutável; apenas impede a reatribuição do identificador. Se o valor for um objeto, suas propriedades ainda podem ser alteradas.
Escopo de função vs bloco
O escopo determina onde uma variável pode ser acessada. var tem escopo de função (function scope), ou seja, é visível em toda a função onde foi declarada, independentemente de blocos internos. Já let e const têm escopo de bloco (block scope), limitado ao bloco { } onde foram declarados.
Exemplo com var:
function exemploVar() {
if (true) {
var x = 10;
}
console.log(x); // 10, acessível fora do bloco
}
Exemplo com let:
function exemploLet() {
if (true) {
let y = 20;
}
console.log(y); // ReferenceError: y is not defined
}
Essa diferença torna let e const mais seguros, pois evitam vazamento de variáveis para escopos maiores do que o necessário.
Hoisting
Hoisting é o comportamento do JavaScript de mover declarações para o topo do escopo antes da execução. Com var, a declaração é içada (hoisted) e inicializada com undefined, permitindo acesso antes da linha de declaração. Já let e const também são içadas, mas não são inicializadas, permanecendo na "zona morta temporal" (temporal dead zone) até a declaração.
Exemplo com var:
console.log(a); // undefined (não causa erro)
var a = 5;
Exemplo com let:
console.log(b); // ReferenceError: Cannot access 'b' before initialization
let b = 10;
O mesmo vale para const. Esse comportamento incentiva boas práticas, pois acessar uma variável antes de sua declaração é quase sempre um erro lógico.
Quando usar cada uma
Atualmente, a recomendação geral é evitar var em novos códigos, devido ao seu escopo de função e comportamento de hoisting que podem levar a bugs inesperados. Prefira const sempre que o valor não precisar ser reatribuído, e use let apenas quando a reatribuição for necessária (por exemplo, em contadores de laços).
Boas práticas:
- Use
constpor padrão. - Use
letquando precisar reatribuir. - Evite
varem código moderno. - Declare variáveis no topo do escopo de bloco para evitar confusão.
Exemplo em um laço for:
// Com let, cada iteração tem seu próprio i
for (let i = 0; i < 5; i++) {
setTimeout(() => console.log(i), 100); // 0,1,2,3,4
}
// Com var, todas as iterações compartilham o mesmo i
for (var j = 0; j < 5; j++) {
setTimeout(() => console.log(j), 100); // 5,5,5,5,5
}
Isso mostra como let resolve um problema clássico de closures em laços.
Referências
Exercícios
Qual será a saída do código abaixo e por quê?
console.log(x); var x = 5; console.log(x);✓ Resposta: A saída seráundefinede depois5. Isso ocorre porque a declaraçãovar xé hoisted para o topo, mas a atribuição permanece no lugar. Então, o primeiroconsole.logencontraxcomoundefined.O código a seguir funciona? Explique.
if (true) { let a = 1; } console.log(a);✓ Resposta: Não, gera umReferenceErrorporquelettem escopo de bloco. A variávelasó existe dentro do blocoif, e oconsole.logestá fora dele.Converta o código abaixo para usar
letouconstde forma adequada, eliminando o uso devar:var nome = "Ana"; var idade = 25; idade = 26; var PI = 3.14;✓ Resposta:const nome = "Ana"; let idade = 25; idade = 26; const PI = 3.14;Qual a diferença entre
conste imutabilidade? Dê um exemplo.✓ Resposta:constimpede a reatribuição do identificador, mas o valor em si pode ser mutável. Exemplo:const obj = { a: 1 }; obj.a = 2; // permitido. Se quiser imutabilidade, useObject.freezeou bibliotecas como Immutable.js.Explique o que é a "zona morta temporal" (temporal dead zone) com um exemplo.
✓ Resposta: A zona morta temporal é o período entre a entrada no escopo e a declaração real da variável comletouconst, durante o qual a variável não pode ser acessada. Exemplo:console.log(x); // ReferenceError let x = 10;