Pular para o conteúdo
Cursos

DevClub

LógicaFront-endBack-endMobile

IA Club

IA na prática
Estudar programaçãoEstudar IA
LiçãoIniciantecódigo testado

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.

Rodolfo Mori7 min de leitura

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:

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.

html head body meta title h1 ul

Percorrer essa árvore em JavaScript é só descer pela propriedade children de cada nó:

js
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);
html head meta title body h1 ul li li script

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.

js
console.log(document.title);
console.log(document.body.tagName);
console.log(document.documentElement.lang);
console.log(document.querySelectorAll('.item').length);
Club Store — Carrinho BODY pt-BR 2

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.

js
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);
object true true function undefined /carrinho.html

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.

js
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));
}
childNodes: 5 children: 2 3 "#text" "\n " 1 "LI" null 3 "#text" "\n " 1 "LI" null 3 "#text" "\n "

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:

js
const titulo = document.getElementById('titulo');
console.log(titulo.textContent);

Rodando no navegador, imprime Seu carrinho. Rodando no Node com node carrinho.mjs:

file:///private/tmp/loja/carrinho.mjs:1 const titulo = document.getElementById('titulo'); ^

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:

js
console.log(typeof window);
console.log(typeof globalThis);
console.log(typeof document);
undefined object undefined

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:

html
<head>
  <meta charset="utf-8">
  <title>Club Store — Carrinho</title>
  <script src="carrinho.js"></script>
</head>
<body>
  <h1 id="titulo">Seu carrinho</h1>
</body>
js
const titulo = document.getElementById('titulo');

console.log(titulo);
titulo.textContent = 'Carrinho (2 itens)';
null Uncaught TypeError: Cannot set properties of null (setting 'textContent') at carrinho.js:4:20

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="">:

js
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);
});
script rodou : loading DOMContentLoaded: interactive - h1: Seu carrinho load : complete - imagens: 1

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:

html
<script src="carrinho.js" defer></script>
js
const titulo = document.getElementById('titulo');

console.log(titulo.tagName, '-', titulo.textContent);
titulo.textContent = 'Carrinho (2 itens)';
console.log(document.querySelector('h1').textContent);
H1 - Seu carrinho Carrinho (2 itens)

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.

Ver todos os vídeos do canal
  • dom
  • document
  • window
  • navegador
  • domcontentloaded

Perguntas frequentes

O DOM faz parte da linguagem JavaScript?
Não. A linguagem é definida pela ECMAScript e vai até Array, Promise e afins. O DOM é uma API que o navegador oferece por cima, especificada pelo WHATWG. Por isso o mesmo código quebra no Node e roda no Chrome.
Qual a diferença entre nó e elemento?
Elemento é um tipo de nó — o que veio de uma tag. Texto e comentário também são nós, e é por isso que childNodes costuma devolver um número maior que children na mesma lista.
Preciso usar DOMContentLoaded se já uso defer?
Não. Um script com defer só executa depois que o HTML terminou de ser lido, que é exatamente a garantia que o DOMContentLoaded dá. Usar os dois juntos não é erro, é redundância.
Posso manipular o DOM com Node no servidor?
Com uma biblioteca que implemente a API, como jsdom ou linkedom, sim. O Node puro não tem document nem window, e nenhuma versão nova vai ter — não é uma peça que esteja faltando, é outro ambiente.

Dúvidas e comentários

Travou em algum passo? Pergunte aqui — a equipe e outros alunos respondem.

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

  1. MDN — Introdução ao DOM — developer.mozilla.org
  2. WHATWG — DOM Living Standard — dom.spec.whatwg.org

Continue por aqui