O que é o DOM em JavaScript e como funciona
O DOM é a árvore de objetos que o navegador monta a partir do HTML. Entenda document, window, nós e por que nada disso existe quando você roda no Node.
DOM é a árvore de objetos que o navegador monta na memória a partir do seu HTML. Cada tag do arquivo vira um objeto JavaScript com propriedades e métodos, e é mexendo nesses objetos que você troca um texto, some com um bloco ou acrescenta um item ao carrinho sem recarregar a página.
A sigla é Document Object Model — modelo de objetos do documento. O nome é
literal: o HTML é o documento, e o DOM é o modelo dele em forma de objeto. O
arquivo .html no disco não muda nunca; quem muda é a árvore.
Uma coisa precisa ficar clara desde já, porque ela explica metade dos erros
desta lição: o DOM não faz parte da linguagem JavaScript. É uma API que o
navegador empresta. Rodar o mesmo arquivo no Node não dá um resultado diferente
— dá um ReferenceError.
Do mapa da loja à árvore de objetos
O arquivo HTML é como o mapa impresso de uma loja: ele descreve entrada, corredores, prateleiras e placas. O DOM é a loja montada a partir desse mapa. Trocar uma placa na loja não apaga nem reimprime o mapa original; muda a estrutura que as pessoas estão usando naquele momento.
Tecnicamente, o navegador faz o parsing do HTML e cria uma árvore de nós em
memória. Cada elemento ganha propriedades e métodos que o JavaScript consegue
ler e alterar. Na primeira microprática, compare o texto do arquivo com o valor
de document.body: se o script mudar o segundo e o primeiro continuar igual no
disco, você acabou de observar a diferença entre documento e modelo.
Esta é a página que vou usar no resto da lição. Salve como carrinho.html:
<!doctype html>
<html lang="pt-BR">
<head>
<meta charset="utf-8">
<title>Club Store — Carrinho</title>
</head>
<body>
<h1 id="titulo">Seu carrinho</h1>
<ul class="itens">
<li class="item">Teclado mecânico</li>
<li class="item">Mouse sem fio</li>
</ul>
<script src="carrinho.js"></script>
</body>
</html>O navegador lê esse texto de cima para baixo e monta uma hierarquia: html
contém head e body, body contém h1, ul e script, e ul contém os
dois li. Cada retângulo abaixo é um objeto de verdade, com o qual você pode
conversar.
Percorrer essa árvore em JavaScript é só descer pela propriedade children de
cada nó:
function desenhar(no, nivel = 0) {
console.log(' '.repeat(nivel) + no.nodeName.toLowerCase());
for (const filho of no.children) desenhar(filho, nivel + 1);
}
desenhar(document.documentElement);O próprio <script> aparece na lista. Ele é uma tag como qualquer outra, e o
código que está rodando enxerga o elemento que o carregou.
document: a porta de entrada da árvore
document é o objeto que representa a página inteira. Toda busca começa nele.
console.log(document.title);
console.log(document.body.tagName);
console.log(document.documentElement.lang);
console.log(document.querySelectorAll('.item').length);Três detalhes que costumam pegar de surpresa. document.title é o texto da tag
<title>, e escrever nele muda o que aparece na aba do navegador.
tagName volta em maiúsculas em documentos HTML — é assim na especificação, não
é bug. E document.documentElement é o <html>, não o <body>: o body tem
propriedade própria justamente porque é o que você mais usa.
window e document não são a mesma coisa
window é o objeto global do navegador — a aba inteira. document é uma
propriedade dele, que representa só a página carregada ali dentro.
console.log(typeof window);
console.log(window === globalThis);
console.log(window.document === document);
console.log(typeof window.setTimeout, typeof document.setTimeout);
console.log(window.location.pathname);Como window é o objeto global, tudo que mora nele você chama sem prefixo:
setTimeout, alert, fetch e localStorage são propriedades de window.
document.setTimeout não existe, porque temporizador é assunto da janela, não
do documento.
A divisão prática: window é o navegador, document é a página. Rolagem,
histórico, tamanho da tela e URL vivem em window. Tags, texto e atributos
vivem em document.
Nó não é elemento
Aqui mora a confusão que faz length vir com um número inesperado. Na árvore do
DOM, tudo é nó: elemento é nó, texto é nó, comentário é nó. Elemento é
apenas o nó que nasceu de uma tag.
const lista = document.querySelector('.itens');
console.log('childNodes:', lista.childNodes.length);
console.log('children: ', lista.children.length);
for (const no of lista.childNodes) {
console.log(no.nodeType, JSON.stringify(no.nodeName), JSON.stringify(no.nodeValue));
}A <ul> tem dois <li>, mas cinco nós filhos. Os três extras são os espaços e
quebras de linha entre as tags — o navegador não joga fora a formatação do seu
HTML, ele guarda como nó de texto.
| propriedade | devolve | conta texto e comentário |
|---|---|---|
childNodes |
NodeList com todos os nós |
sim |
children |
HTMLCollection só de elementos |
não |
firstChild |
primeiro nó, seja qual for | sim |
firstElementChild |
primeiro nó que é elemento | não |
nodeType |
1 elemento · 3 texto · 8 comentário |
— |
Na prática do dia a dia você quer children, firstElementChild e
nextElementSibling. As versões sem Element no nome existem para quando você
precisa mexer no texto solto — e para te dar um bug quando você usa a errada.
O DOM não faz parte do JavaScript
Este é o teste que fecha a questão. O arquivo abaixo é JavaScript válido, sem nenhum erro de sintaxe:
const titulo = document.getElementById('titulo');
console.log(titulo.textContent);Rodando no navegador, imprime Seu carrinho. Rodando no Node com
node carrinho.mjs:
ReferenceError: document is not defined at file:///private/tmp/loja/carrinho.mjs:1:16 at ModuleJob.run (node:internal/modules/esm/module_job:439:25) at async node:internal/modules/esm/loader:633:26 at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:101:5)
Node.js v24.16.0
Repare na mensagem: não é “document não é uma função” nem “document está vazio”.
É is not defined — o nome não existe em lugar nenhum. O Node tem um objeto
global, mas ele não guarda nada de página:
console.log(typeof window);
console.log(typeof globalThis);
console.log(typeof document);Guarde a divisão. Array, Math, Promise, JSON e String são a linguagem:
funcionam nos dois lugares. document, window, alert, localStorage e
fetch de página são o navegador. O contrário também acontece: require,
process e fs só existem no Node.
Quando a árvore fica pronta: DOMContentLoaded e load
Se o seu script roda antes de o navegador terminar de montar a árvore, ele não
encontra os elementos — porque eles ainda não existem. É o que acontece com um
<script> no <head> sem defer:
<head>
<meta charset="utf-8">
<title>Club Store — Carrinho</title>
<script src="carrinho.js"></script>
</head>
<body>
<h1 id="titulo">Seu carrinho</h1>
</body>const titulo = document.getElementById('titulo');
console.log(titulo);
titulo.textContent = 'Carrinho (2 itens)';O console.log já entrega o diagnóstico: null. A busca funcionou, só não
achou nada — quando ela rodou, o <h1> ainda não tinha sido lido. É o mesmo
mecanismo por trás do
TypeError: Cannot read properties of undefined,
com null no lugar de undefined.
Dois eventos avisam quando dá para trabalhar. Para o load ter o que esperar,
acrescente uma imagem ao <body> da página — <img src="banner.png" alt="">:
console.log('script rodou :', document.readyState);
document.addEventListener('DOMContentLoaded', () => {
console.log('DOMContentLoaded:', document.readyState, '- h1:', document.querySelector('h1').textContent);
});
window.addEventListener('load', () => {
console.log('load :', document.readyState, '- imagens:', document.images.length);
});DOMContentLoaded dispara assim que o HTML foi lido inteiro e a árvore está
montada. load espera também as imagens, fontes e folhas de estilo terminarem
de baixar — pode ser bem depois, e numa conexão ruim é uma eternidade.
Para mexer no DOM você quer DOMContentLoaded. load só serve quando você
precisa de algo que depende do arquivo baixado, como a altura real de uma
imagem.
A solução de uma palavra: defer
Na prática moderna você nem precisa do evento. Basta o atributo defer na tag
do script — ele adia a execução para depois da leitura do HTML, sem travar o
carregamento da página:
<script src="carrinho.js" defer></script>const titulo = document.getElementById('titulo');
console.log(titulo.tagName, '-', titulo.textContent);
titulo.textContent = 'Carrinho (2 itens)';
console.log(document.querySelector('h1').textContent);Mesmo script, mesmo lugar no <head>, resultado oposto.
O que fazer com isso agora
O DOM é um assunto grande, mas o caminho de entrada é curto e sempre o mesmo: achar o elemento, ler ou escrever nele, reagir a um evento. As próximas lições da trilha de JavaScript seguem exatamente essa ordem — a primeira delas é selecionar elementos com querySelector e getElementById.
Antes de seguir, abra o DevTools do seu navegador e faça três coisas na página
que você tem aberta agora. Digite document.title no console. Digite
document.querySelectorAll('a').length. Depois abra a aba Elements e repare
que ela não mostra o seu arquivo HTML: mostra a árvore, com as alterações que o
JavaScript já fez. Ver a diferença entre as duas coisas é o que faz o resto
encaixar.
Se ainda estiver montando a base da linguagem antes de mexer em página, volte para variáveis e funções — o guia completo de JavaScript mostra em que ponto da trilha cada assunto entra.
Prefere aprender em vídeo?
Tem uma aula sobre este assunto no nosso canal.
Perguntas frequentes
O DOM faz parte da linguagem JavaScript?
Qual a diferença entre nó e elemento?
Preciso usar DOMContentLoaded se já uso defer?
Posso manipular o DOM com Node no servidor?
Dúvidas e comentários
Travou em algum passo? Pergunte aqui — a equipe e outros alunos respondem.
Entrar para perguntarÉ o mesmo login gratuito dos cursos.
Nenhuma dúvida por aqui ainda — a primeira pode ser a sua.
Todo o código deste artigo foi executado em Node 24.16.0 e Chromium 151.0.7922.34, e as saídas exibidas são as reais — como produzimos este conteúdo.
Fontes consultadas
- MDN — Introdução ao DOM — developer.mozilla.org
- WHATWG — DOM Living Standard — dom.spec.whatwg.org



