JavaScript é a linguagem que faz a página reagir: clicar, digitar, filtrar, guardar e buscar dados. É o quarto artigo da trilha "Quero virar dev em casa", e ele termina com uma lista de tarefas que funciona de verdade e guarda os dados no navegador. Pré-requisitos: lógica de programação e uma página HTML e CSS, como a do artigo 3.
Para quem é este artigo
Para quem já fez a primeira página e quer acrescentar interação. Tempo de estudo sugerido: de 10 a 15 horas ao longo de duas a três semanas, entre leitura, vídeos e prática (estimativa da Redação).
O que você vai aprender
- Variáveis, tipos, arrays e objetos em JavaScript.
- O DOM: como o JavaScript enxerga e muda a página.
- Eventos: reagir a cliques e formulários.
fetch: carregar dados de um arquivo ou de uma API.localStorage: guardar dados no navegador.- Um mini-projeto: lista de tarefas.
O que é JavaScript e por que é uma boa primeira linguagem
JavaScript é a linguagem que roda dentro do navegador. HTML dá a estrutura, CSS dá a aparência e JavaScript dá o comportamento. Por rodar em qualquer navegador, não exige instalar nada além do que você já tem, e o resultado aparece na hora. Esta é a razão pela qual a Redação recomenda começar pela web, como no primeiro artigo da trilha.
Os fundamentos que você já viu
No artigo de lógica, você usou variáveis, condições, repetições e funções, em Python e em JavaScript. A ideia é a mesma aqui. O MDN recomenda declarar variáveis com const quando puder e let quando precisar.
Tipos e comparações
console.log(typeof 42, typeof "oi", typeof true, typeof undefined);
// number string boolean undefined
console.log(3 === "3", 3 == "3");
// false true
Use sempre === (igualdade estrita), que não converte tipos. O == converte e leva a resultados surpreendentes, como 3 == "3" ser verdadeiro. Esta é uma recomendação da Redação.
Arrays: listas de valores
const tarefas = ["Estudar HTML", "Estudar CSS"];
tarefas.push("Publicar no GitHub Pages");
console.log(tarefas.length, tarefas[0]);
// 3 Estudar HTML
for (const tarefa of tarefas) {
console.log("-", tarefa);
}
A contagem começa em zero: tarefas[0] é o primeiro item.
Objetos: dados com nome
const pessoa = { nome: "Ana", idade: 28, estuda: true };
console.log(pessoa.nome, pessoa["idade"]);
// Ana 28
pessoa.cidade = "Recife";
Arrays e objetos juntos descrevem quase qualquer dado: uma lista de tarefas é um array de objetos, cada um com texto e estado. E o método filter cria um novo array só com o que passa em um teste:
const pendentes = tarefas.filter((t) => !t.concluida);
O DOM: como o JavaScript enxerga a página
Segundo o MDN, o DOM é uma representação em árvore da estrutura do HTML, criada pelo navegador, que permite acessá-la por linguagens de programação. Cada item da árvore é um nó. O MDN recomenda selecionar elementos com querySelector, que aceita seletores de CSS:
const botao = document.querySelector("#tema");
Com a referência na mão, você pode mudar o texto (textContent), criar elementos (createElement), colocá-los na página (appendChild) e retirá-los (remove):
const item = document.createElement("li");
item.textContent = "Estudar JavaScript";
lista.appendChild(item);
Eventos: reagir ao que o usuário faz
Segundo o MDN, eventos são coisas que acontecem no sistema e sobre as quais ele avisa para o seu código reagir. Para reagir, você anexa um ouvinte de evento com addEventListener, o mecanismo recomendado pelo MDN. Ele recebe o tipo do evento (como "click") e a função a executar:
botao.addEventListener("click", () => {
document.body.classList.toggle("escuro");
});
Em formulários, o navegador por padrão envia e recarrega a página. O MDN mostra como preventDefault() impede esse comportamento padrão, e é o que faremos na lista de tarefas.
Exemplo 1: alternar o tema e lembrar a escolha
Acrescente um botão ao cabeçalho do currículo do artigo 3 e um arquivo script.js. No HTML, dentro do <header>, depois do <nav>:
<button id="tema" type="button">Alternar tema</button>
E, antes do </body>:
<script src="script.js"></script>
No CSS, acrescente o visual escuro:
body.escuro { background: #0b1520; color: #e4ebf2; }
body.escuro h2 { color: #9fb3c8; }
body.escuro .cartoes li { background: #16283a; }
body.escuro main a { color: #9fb3c8; }
E no script.js:
const botaoTema = document.querySelector("#tema");
if (localStorage.getItem("tema") === "escuro") {
document.body.classList.add("escuro");
}
botaoTema.addEventListener("click", () => {
const escuro = document.body.classList.toggle("escuro");
localStorage.setItem("tema", escuro ? "escuro" : "claro");
});
O MDN explica que o localStorage guarda pares de chave e valor, sempre como texto, e que os dados persistem mesmo quando o navegador é fechado e reaberto. A página recomenda usar setItem e getItem. Resultado testado: o botão alterna o tema, e a escolha continua depois de recarregar a página. O teste também achou um problema de contraste: os links de contato ficavam azul-escuros sobre fundo escuro, e a regra body.escuro main a acima o corrige.
Cuidado (da Redação): não guarde senhas nem dados sensíveis no localStorage; qualquer script da página consegue lê-lo.
fetch: carregar dados de um arquivo ou de uma API
Segundo o MDN, fetch() é uma API assíncrona que devolve uma Promise. Você encadeia then() para tratar a resposta, chama response.json() para obter os dados como objeto e confere response.ok, para detectar erros como o 404. Um catch() no fim trata falhas.
Crie um arquivo projetos.json:
[
{ "nome": "Currículo online", "descricao": "minha primeira página, publicada no GitHub Pages" },
{ "nome": "Lista de tarefas", "descricao": "adicionar, concluir, remover e filtrar tarefas" }
]
Acrescente <ul id="lista-projetos"></ul> na seção de projetos do HTML e, no script.js:
const listaProjetos = document.querySelector("#lista-projetos");
fetch("projetos.json")
.then((resposta) => {
if (!resposta.ok) {
throw new Error(`Erro HTTP: ${resposta.status}`);
}
return resposta.json();
})
.then((projetos) => {
for (const projeto of projetos) {
const item = document.createElement("li");
item.textContent = `${projeto.nome}: ${projeto.descricao}`;
listaProjetos.appendChild(item);
}
})
.catch((erro) => {
listaProjetos.textContent = "Não foi possível carregar os projetos.";
console.error(`Problema no fetch: ${erro.message}`);
});
Resultado testado: os dois projetos aparecem na lista. Atenção: o fetch de um arquivo local costuma falhar se você abrir o HTML com duplo clique (endereço file://). Teste por um servidor, e o GitHub Pages já é um (a Redação rodou todos os testes por um servidor local e não testou o caso do duplo clique). Em um editor de código, procure uma extensão de servidor local, ou rode python3 -m http.server na pasta. A página do MDN não cobre async/await; ele é uma forma equivalente de escrever, e você o encontra nos vídeos.
Mini-projeto: lista de tarefas
O usuário pode adicionar, concluir, remover e filtrar tarefas, e os dados ficam salvos no navegador. Crie tarefas.html, tarefas.css e tarefas.js.
tarefas.html
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<title>Lista de tarefas</title>
<link rel="stylesheet" href="tarefas.css" />
</head>
<body>
<main>
<h1>Lista de tarefas</h1>
<form id="formulario">
<label for="texto">Nova tarefa</label>
<input id="texto" type="text" maxlength="80" autocomplete="off" />
<button type="submit">Adicionar</button>
</form>
<div class="filtros">
<button type="button" data-filtro="todas">Todas</button>
<button type="button" data-filtro="pendentes">Pendentes</button>
<button type="button" data-filtro="concluidas">Concluídas</button>
</div>
<ul id="lista"></ul>
<p id="contador"></p>
</main>
<script src="tarefas.js"></script>
</body>
</html>
tarefas.css
body { margin: 0; font-family: system-ui, sans-serif; background: #f5f7fa; color: #1f2933; }
main { max-width: 32rem; margin: 0 auto; padding: 1.5rem; }
form { display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: center; }
form label { width: 100%; font-weight: 600; }
form input { flex: 1; padding: 0.5rem; }
button { padding: 0.5rem 0.8rem; cursor: pointer; }
.filtros { display: flex; gap: 0.5rem; margin: 1rem 0; }
.filtros button[aria-pressed="true"] { background: #102a43; color: white; }
ul { padding: 0; list-style: none; }
li { display: flex; align-items: center; gap: 0.5rem; padding: 0.6rem 0; border-bottom: 1px solid #d9e2ec; }
li span { flex: 1; }
li.concluida span { text-decoration: line-through; color: #7b8794; }
tarefas.js
const formulario = document.querySelector("#formulario");
const campoTexto = document.querySelector("#texto");
const lista = document.querySelector("#lista");
const contador = document.querySelector("#contador");
const botoesFiltro = document.querySelectorAll("[data-filtro]");
let tarefas = JSON.parse(localStorage.getItem("tarefas") || "[]");
let filtro = "todas";
function salvar() {
localStorage.setItem("tarefas", JSON.stringify(tarefas));
}
function desenhar() {
lista.textContent = "";
const visiveis = tarefas.filter((t) => {
if (filtro === "pendentes") return !t.concluida;
if (filtro === "concluidas") return t.concluida;
return true;
});
for (const tarefa of visiveis) {
const item = document.createElement("li");
if (tarefa.concluida) item.classList.add("concluida");
const caixa = document.createElement("input");
caixa.type = "checkbox";
caixa.checked = tarefa.concluida;
caixa.setAttribute("aria-label", `Concluir: ${tarefa.texto}`);
caixa.addEventListener("change", () => {
tarefa.concluida = caixa.checked;
salvar();
desenhar();
});
const texto = document.createElement("span");
texto.textContent = tarefa.texto;
const remover = document.createElement("button");
remover.type = "button";
remover.textContent = "Remover";
remover.addEventListener("click", () => {
tarefas = tarefas.filter((t) => t.id !== tarefa.id);
salvar();
desenhar();
});
item.append(caixa, texto, remover);
lista.appendChild(item);
}
const pendentes = tarefas.filter((t) => !t.concluida).length;
contador.textContent = `${pendentes} pendente(s) de ${tarefas.length}`;
for (const botao of botoesFiltro) {
botao.setAttribute("aria-pressed", String(botao.dataset.filtro === filtro));
}
}
formulario.addEventListener("submit", (evento) => {
evento.preventDefault();
const texto = campoTexto.value.trim();
if (texto === "") return;
tarefas.push({ id: crypto.randomUUID(), texto, concluida: false });
campoTexto.value = "";
salvar();
desenhar();
});
for (const botao of botoesFiltro) {
botao.addEventListener("click", () => {
filtro = botao.dataset.filtro;
desenhar();
});
}
desenhar();
Como a Redação testou, e o bug que o teste achou
A Redação rodou a lista no Chrome, servida por um servidor local, e verificou: tarefa adicionada, texto vazio recusado, tarefa concluída (com o texto riscado), filtros de pendentes e concluídas, remoção e contador. Depois recarregou a página, e as tarefas continuaram lá.
O teste achou um defeito na primeira versão. O identificador de cada tarefa vinha de Date.now(), a hora em milissegundos. Quando três tarefas eram criadas no mesmo milissegundo, todas ficavam com o mesmo identificador, e remover uma removia todas, porque o código remove "todas as tarefas com esse identificador". Um humano raramente clica tão rápido, mas é um defeito real, e ele se mostrou assim que o teste adicionou tarefas em sequência. A versão acima usa crypto.randomUUID(), que gera um identificador único, e foi testada de novo: remover uma tarefa remove só ela.
A lição vale para toda a sua carreira: teste o caminho feliz e também o caso esquisito. Um bug que "nunca acontece" costuma acontecer no pior momento.
Vídeos para estudar
Os quatro vídeos foram verificados quanto ao título, ao canal, à duração, à data e à possibilidade de incorporação. A Redação não os assistiu na íntegra nem verificou o que dizem.
Vídeo: Fernanda Kipper
Vídeo: Curso em Vídeo
Vídeo: Hashtag Dev
Vídeo: freeCodeCamp em Português
Exercícios práticos
- Contador de cliques: um botão que mostra quantas vezes foi clicado.
- Saudação: um campo de nome e um botão que escreve "Olá, [nome]" na página.
- Tema com o tema do sistema: descubra como ler a preferência de tema do computador e use-a como padrão.
- Editar tarefa: na lista, permita editar o texto de uma tarefa.
- Prioridade: acrescente uma prioridade (alta, média, baixa) a cada tarefa e ordene a lista.
- Erro no fetch: troque o nome do arquivo no
fetchpor um que não existe e veja a mensagem de erro aparecer.
Erros comuns de iniciantes
- Selecionar o elemento antes de ele existir. Coloque o
<script>no fim do<body>, como nos exemplos. - Usar
==em vez de===. - Esquecer o
preventDefault()no formulário, e a página recarrega ao enviar. - Guardar objeto direto no
localStorage. Ele só guarda texto: useJSON.stringifypara gravar eJSON.parsepara ler. - Abrir o HTML com duplo clique e usar
fetch. Use um servidor local. - Identificadores repetidos. O bug desta matéria: use um gerador de identificador único.
- Copiar sem entender. Escreva à mão e quebre de propósito.
- Não olhar o console. Aperte F12 e leia as mensagens de erro.
Checklist de conclusão
- Sei criar arrays e objetos e percorrer um array.
- Selecionei elementos com
querySelectore mudei o conteúdo da página. - Usei
addEventListenerpara reagir a cliques e a formulários. - Carreguei dados com
fetche tratei o erro. - Guardei e li dados no
localStoragecomJSON. - Terminei a lista de tarefas e testei os casos esquisitos.
- Sei abrir o console do navegador e ler um erro.
Próximo passo da trilha
Agora que você tem projetos no computador, o próximo artigo ensina Git e GitHub: guardar a história do seu código, publicar a lista de tarefas e montar um portfólio.
Perguntas frequentes
O que é JavaScript?
É a linguagem que roda no navegador e dá comportamento à página: reage a cliques, digitação e envio de formulários, muda o conteúdo e busca dados.
JavaScript é uma boa primeira linguagem?
É uma boa porta de entrada pela web, porque roda no navegador que você já tem e mostra o resultado na hora. A Redação também recomenda aprender lógica antes, como no artigo anterior. É uma recomendação, e não uma regra.
O que é o DOM?
Segundo o MDN, é uma representação em árvore da estrutura do HTML, criada pelo navegador, que permite que linguagens de programação a acessem e a modifiquem.
O que é um evento em JavaScript?
Segundo o MDN, são coisas que acontecem no sistema e sobre as quais ele avisa, para o seu código reagir, como um clique. Você reage a elas com addEventListener.
O que é fetch?
Segundo o MDN, é uma API assíncrona que devolve uma Promise e serve para buscar dados em um endereço, como um arquivo JSON ou uma API.
O que o localStorage guarda e por quanto tempo?
Segundo o MDN, guarda pares de chave e valor como texto, e os dados persistem mesmo com o navegador fechado e reaberto. Para objetos e arrays, use JSON.stringify.
Posso guardar senhas no localStorage?
Não. Qualquer script da página consegue ler o localStorage. Esta é uma recomendação da Redação.
Por que o fetch não funciona quando abro o HTML com duplo clique?
Em geral, porque o endereço começa com file:// e o navegador bloqueia a leitura de arquivos por fetch. Use um servidor local ou publique no GitHub Pages. Esta é uma observação da Redação, e o caso do endereço file:// não foi testado por ela.
Fontes
- DOM scripting introduction, MDN Web Docs (Mozilla)
- Introduction to events, MDN Web Docs (Mozilla)
- Network requests (fetch), MDN Web Docs (Mozilla)
- Using the Web Storage API, MDN Web Docs (Mozilla)
- Storing the information you need: Variables, MDN Web Docs (Mozilla)
