DOM, Eventos e Assincronismo
O que é o DOM · Selecionar e manipular elementos · Eventos · Promises e async/await
Até aqui, JavaScript existia isolado: variáveis, funções, loops. Nesta aula ele passa a conversar com a página: ler e alterar o HTML que já está na tela, reagir ao que o usuário faz, e buscar dados que ainda não chegaram.
1. O que é o DOM
O DOM (Document Object Model) é a representação do HTML da página como uma árvore de objetos que o JavaScript consegue ler e modificar em tempo real. Quando você altera o DOM, a tela atualiza sem recarregar a página.
| Camada | O que é |
|---|---|
| HTML (arquivo) | O código-fonte estático que o navegador recebeu |
| DOM (em memória) | A árvore de objetos viva, que o navegador renderiza e o JS manipula |
Relação com a Aula 01 de HTML
A árvore de elementos que você viu em HTML (pais, filhos, irmãos) é exatamente a estrutura que o DOM expõe para o JavaScript.
2. Selecionando elementos
// Por id: retorna um único elemento
const titulo = document.getElementById('titulo');
// Por seletor CSS: retorna o primeiro que casar
const primeiroItem = document.querySelector('.item');
// Por seletor CSS: retorna TODOS que casarem (NodeList)
const todosItens = document.querySelectorAll('.item');Boa prática
Prefira querySelector/querySelectorAll: eles aceitam qualquer seletor CSS (classe, id, atributo, combinadores), então você reaproveita o que já sabe de CSS em vez de aprender uma API separada para cada caso.
3. Manipulando HTML com JavaScript
Texto e HTML interno
const titulo = document.querySelector('h1');
titulo.textContent = 'Novo título'; // troca o texto (seguro contra HTML malicioso)
titulo.innerHTML = 'Novo <em>título</em>'; // troca o HTML interno (cuidado com dados de usuário!)textContent vs innerHTML
Use textContent sempre que o conteúdo for texto puro. Só use innerHTML quando precisar inserir HTML de fato. Nunca use com texto vindo direto do usuário sem sanitização, ou você abre a porta para um ataque de XSS (o usuário poderia injetar <script> ou atributos como onerror).
Inserindo HTML em posições específicas
innerHTML sempre substitui o conteúdo inteiro. Quando você quer adicionar HTML sem apagar o que já existe, insertAdjacentHTML deixa escolher exatamente onde:
const caixa = document.querySelector('.caixa');
caixa.insertAdjacentHTML('afterbegin', '<p>Primeiro parágrafo</p>');
caixa.insertAdjacentHTML('beforeend', '<p>Último parágrafo</p>');| Posição | Onde entra |
|---|---|
beforebegin | antes do elemento, como irmão anterior |
afterbegin | dentro do elemento, antes do primeiro filho |
beforeend | dentro do elemento, depois do último filho |
afterend | depois do elemento, como próximo irmão |
Atributos: setAttribute × propriedade × dataset
const link = document.querySelector('a');
link.setAttribute('href', '/nova-pagina'); // define um atributo genérico
link.getAttribute('href'); // lê o valor de um atributo
link.hasAttribute('target'); // true/false
link.removeAttribute('target'); // remove o atributo
// atalho direto para atributos comuns (equivalente, mais lido)
link.href = '/nova-pagina';
// atributos data-* têm uma API própria: dataset
// HTML: <li data-id="42" data-categoria="frutas">
const item = document.querySelector('li');
item.dataset.id; // "42"
item.dataset.categoria; // "frutas"
item.dataset.novo = 'sim'; // cria data-novo="sim" no HTMLClonando e substituindo elementos
const original = document.querySelector('.card');
const copia = original.cloneNode(true); // true = clona também os filhos
document.querySelector('.container').appendChild(copia);
// substituir um elemento inteiro por outro
const novoCard = document.createElement('div');
novoCard.textContent = 'Card atualizado';
original.replaceWith(novoCard);Testar manipulação de HTML ao vivo →
4. Criando, movendo e removendo elementos
O padrão básico: criar → preencher → inserir
const li = document.createElement('li');
li.textContent = 'Novo item';
const lista = document.querySelector('ul');
lista.appendChild(li); // insere como último filho
lista.prepend(li); // ou insere como primeiro filhoRenderizando uma lista a partir de dados
Esse é o padrão mais comum na prática: transformar um array de dados em elementos reais na página.
const frutas = ['maçã', 'banana', 'uva'];
const lista = document.querySelector('#lista-frutas');
lista.innerHTML = ''; // limpa o conteúdo anterior antes de re-renderizar
frutas.forEach((fruta) => {
const li = document.createElement('li');
li.textContent = fruta;
li.classList.add('item-fruta');
lista.appendChild(li);
});DocumentFragment para inserir muitos elementos de uma vez
Cada appendChild direto na página pode forçar o navegador a recalcular o layout. Ao inserir muitos elementos, monte-os primeiro em um DocumentFragment (que não faz parte da página) e insira tudo de uma vez:
const fragment = document.createDocumentFragment();
frutas.forEach((fruta) => {
const li = document.createElement('li');
li.textContent = fruta;
fragment.appendChild(li);
});
lista.appendChild(fragment); // um único recálculo de layoutRemovendo elementos
const item = document.querySelector('.item');
item.remove(); // forma moderna, direta
item.parentElement.removeChild(item); // forma antiga, ainda comum em código legadoTestar renderização de listas ao vivo →
5. Manipulando CSS com JavaScript
Há três formas de alterar a aparência de um elemento via JavaScript, cada uma com seu uso ideal.
| Abordagem | Quando usar |
|---|---|
classList | Padrão para a maioria dos casos: liga/desliga um conjunto de estilos já definido no CSS |
style | Um valor calculado dinamicamente (posição de arrastar, cor escolhida pelo usuário) |
Variáveis CSS via style.setProperty | Um valor dinâmico que afeta vários elementos ao mesmo tempo (tema, escala) |
classList: a forma preferida
const caixa = document.querySelector('.caixa');
caixa.classList.add('destaque'); // adiciona
caixa.classList.remove('escondido'); // remove
caixa.classList.toggle('ativo'); // alterna
caixa.classList.contains('ativo'); // true/false, sem alterar nadaBoa prática
Prefira classList a manipular style diretamente. Deixe o CSS decidir como cada estado parece (cores, bordas, animação) e deixe o JavaScript decidir só quando aplicar aquele estado, a mesma separação de responsabilidades vista na Aula 01 de HTML.
style: propriedades inline individuais
const caixa = document.querySelector('.caixa');
caixa.style.color = 'white'; // propriedades em camelCase (background-color → backgroundColor)
caixa.style.backgroundColor = 'tomato';
caixa.style.padding = '1rem';
// definir várias de uma vez
caixa.style.cssText = 'color: white; background-color: tomato; padding: 1rem;';
// remover um estilo inline específico
caixa.style.color = '';Lendo o estilo realmente aplicado: getComputedStyle
element.style só enxerga estilos inline: não o que veio de um arquivo CSS ou de uma tag <style>. Para ler o valor final, depois de toda a cascata aplicada, use getComputedStyle:
const caixa = document.querySelector('.caixa');
caixa.style.color; // '': vazio, se a cor veio de um arquivo CSS externo
const estilos = getComputedStyle(caixa);
estilos.color; // a cor realmente renderizada, ex: "rgb(255, 0, 0)"
estilos.getPropertyValue('font-size'); // "16px"Variáveis CSS (custom properties)
Variáveis CSS (--nome-da-variavel) definidas no CSS podem ser lidas e alteradas via JavaScript: ideal para temas, porque uma única mudança afeta todos os elementos que usam aquela variável:
:root {
--cor-primaria: #6366f1;
}
.botao {
background: var(--cor-primaria);
}// ler o valor atual da variável
getComputedStyle(document.documentElement).getPropertyValue('--cor-primaria');
// alterar a variável: todo elemento que usa var(--cor-primaria) atualiza junto
document.documentElement.style.setProperty('--cor-primaria', '#22c55e');Exemplo prático: alternador de tema (dark mode)
Combinando classList com variáveis CSS: o padrão real usado por sites com alternância de tema:
:root {
--bg: white;
--texto: black;
}
.dark {
--bg: #1e1e2e;
--texto: white;
}
body {
background: var(--bg);
color: var(--texto);
}const botaoTema = document.querySelector('#alternar-tema');
botaoTema.addEventListener('click', () => {
document.documentElement.classList.toggle('dark');
});Injetando CSS dinamicamente
Para casos mais raros (gerar regras CSS que não existem de antemão), é possível criar uma tag <style> via JavaScript:
const estilo = document.createElement('style');
estilo.textContent = `
.destaque-dinamico {
border: 2px solid orange;
animation: pulsar 1s infinite;
}
`;
document.head.appendChild(estilo);Testar manipulação de CSS ao vivo →
6. Eventos
Um evento é algo que acontece na página: um clique, uma tecla pressionada, um formulário enviado. addEventListener registra uma função para rodar quando esse evento ocorrer.
const botao = document.querySelector('#meu-botao');
botao.addEventListener('click', function (event) {
console.log('Botão clicado!');
console.log(event.target); // o elemento que disparou o evento
});| Evento | Ocorre quando... |
|---|---|
click | o elemento é clicado |
input | o valor de um campo muda, a cada tecla |
submit | um <form> é enviado |
keydown | uma tecla é pressionada |
DOMContentLoaded | o HTML terminou de carregar (em document) |
preventDefault()
Alguns elementos têm um comportamento padrão do navegador (um link navega, um form recarrega a página). preventDefault() cancela esse comportamento para você controlar o que acontece:
const form = document.querySelector('form');
form.addEventListener('submit', function (event) {
event.preventDefault(); // impede o recarregamento da página
console.log('Formulário enviado sem recarregar!');
});7. Assincronismo: código que espera algo
Buscar dados de um servidor, esperar um temporizador: essas operações não terminam instantaneamente. JavaScript lida com isso de forma assíncrona, sem travar o resto da página enquanto espera.
setTimeout e a ordem de execução
console.log('1');
setTimeout(() => {
console.log('2');
}, 0);
console.log('3');
// ordem real no console: 1, 3, 2Mesmo com 0 milissegundos de espera, o setTimeout só executa depois que todo o código síncrono terminar. JavaScript processa uma coisa por vez (é single-threaded) e código assíncrono sempre espera sua vez na fila.
Ver a ordem de execução ao vivo →
Promises
Uma Promise representa um valor que vai existir no futuro: ela pode ser resolvida com sucesso (resolve) ou falhar (reject).
function esperar(segundos) {
return new Promise((resolve) => {
setTimeout(() => resolve('Pronto!'), segundos * 1000);
});
}
esperar(2).then((mensagem) => console.log(mensagem)); // 'Pronto!' depois de 2sasync/await
async/await é uma forma de escrever código com Promises que parece síncrono, mais fácil de ler do que encadear vários .then():
async function buscarDados() {
console.log('Buscando...');
const resposta = await fetch('https://api.exemplo.com/dados');
const dados = await resposta.json();
console.log(dados);
}await só pode ser usado dentro de uma função marcada como async, e ele pausa a execução daquela função (sem travar a página) até a Promise resolver.
Boa prática
Sempre trate erros de código assíncrono com try/catch (junto com async/await) ou .catch() (junto com .then()). Uma requisição de rede pode falhar, e código sem tratamento de erro quebra silenciosamente.
async function buscarDados() {
try {
const resposta = await fetch('https://api.exemplo.com/dados');
const dados = await resposta.json();
console.log(dados);
} catch (erro) {
console.error('Falhou:', erro);
}
}8. Boas práticas: resumo
- Prefira
querySelector/querySelectorAllagetElementById/getElementsByClassName. - Use
textContentpor padrão; só useinnerHTMLquando precisar de HTML de fato, nunca com dados de usuário sem sanitizar. - Prefira
classLista mexer emstylediretamente: deixe o CSS decidir a aparência de cada estado. - Use
getComputedStylepara ler estilo aplicado; usestyle/classListpara alterar. - Ao inserir muitos elementos de uma vez, monte-os em um
DocumentFragmentantes de inserir na página. - Sempre chame
preventDefault()em formulários que você mesmo vai tratar via JS. - Prefira
async/awaita encadear.then(), e sempre trate erros comtry/catch. - Lembre-se: código assíncrono sempre roda depois do código síncrono, mesmo com delay
0.
9. Exercícios
- Seleção: Dado um HTML com três
<li class="item">, usequerySelectorAllpara selecioná-los eforEachpara imprimir o texto de cada um. - Manipulação de HTML: Dado
<ul id="lista"></ul>, use um array de nomes ecreateElement/appendChild(ou umDocumentFragment) para renderizar um<li>para cada nome. - Atributos: Em um elemento com
data-id="7", leia o valor viadatasete depois altere para"8". - Manipulação de CSS: Escreva uma função
alternarTema()que dátogglena classedarkdo<html>e leia, comgetComputedStyle, a cor de fundo resultante. - Variável CSS: Defina
--cor-destaqueno CSS e escreva um código JS que a altera para um valor escolhido pelo usuário via<input type="color">. - Evento: Registre um
clickem um botão que alterna (toggle) uma classeativoem um<div>. - preventDefault: Escreva o
addEventListener('submit', ...)de um formulário que impede o recarregamento e imprime os dados no console. - Async: Escreva uma função
asyncque aguarda 1 segundo (usando a funçãoesperardo exemplo) e depois imprime'Terminou!'.
Fim da série de Fundamentos JavaScript. Próximo passo natural: praticar combinando DOM + eventos + fetch em um projeto pequeno (uma lista de tarefas, um buscador de CEP, um consumo de API pública).