Dia 11 — A página: document e querySelector

Informatica · Conteudo · publicado em 02/10/2026
Dia 11 de 13

A página: document e querySelector

Aula 1

DOM e a arvore do HTML

O DOM e a árvore do HTML

document é o objeto que dá acesso à página inteira. Tudo que o JavaScript faz no navegador passa por ele: ler um texto, trocar uma classe, criar um elemento. O nome completo é DOM (*Document Object Model*), e ele é a estrutura de objetos que o navegador monta depois de ler o arquivo HTML.

O que o DOM é

O HTML é um arquivo de texto. O DOM é a árvore DOM: a árvore de objetos que o navegador constrói a partir desse arquivo, já com as tags resolvidas, os atributos guardados e os elementos linkados por pai e filho.

Cada elemento do HTML vira um objeto JavaScript. A tag <h1 id="titulo">Olá</h1> vira um objeto com propriedades: o id é "titulo", o texto é "Olá", e o objeto tem pai e filhos. É essa árvore que o document entrega.

A raiz é o elemento html

A árvore tem um único nó raiz, e ele é o elemento <html>. Dentro dele vêm sempre dois filhos, nesta ordem: <head> (metadados, título, <script>, <style>) e <body> (tudo que aparece na tela).

NívelNóO que é
0htmla raiz, document.documentElement
1headtítulo, <script>, <style>, meta
1bodytudo que o usuário vê na página
2h1 id="titulo"filho do body, com id e texto
2p class="destaque"filho do body, irmão do h1

Os dois filhos do <body> são irmãos: mesmo pai, mesmo nível. É essa vizinhança que o querySelector e os métodos de percorredor exploram.

DOM não é o arquivo HTML

O arquivo que chega do servidor é o HTML. O DOM é o que existe na memória depois que o navegador leu esse arquivo — e os dois divergem o tempo todo, porque o JavaScript altera o DOM sem alterar o arquivo. A pergunta "DOM vs HTML" tem resposta curta: HTML é o texto que veio do servidor, DOM é o estado atual da página.

Recarregar a página descarta todo o DOM montado e começa de novo a partir do HTML. Por isso um appendChild que o aluno não salva em lugar nenhum some ao dar F5.

document, window e globalThis

São três portas de entrada para a mesma página, com escopos diferentes. window é o objeto global do navegador; document é uma das propriedades dele; globalThis é o nome padronizado do global, e vale tanto no navegador quanto no Node.

As três portas de entrada

ObjetoO que éAssinatura de acessoExemplo
windowobjeto global do navegadorwindow.documentwindow.alert('oi')
documenta página, raiz do DOMdocument.bodydocument.title
globalThiso global, com nome padronizadoglobalThis.documentglobalThis.setTimeout(...)

No navegador, globalThis === window. O document também é acessível sem escrever nada: document.querySelector(...) funciona porque document é uma propriedade do global e o JavaScript resolve o nome sozinho.

O que costuma ser lido no document

PropriedadeAssinaturaO que devolve
document.documentElementdocument.documentElemento <html>, a raiz da árvore
document.headdocument.heado <head>, com title e link
document.bodydocument.bodyo <body>, o conteúdo visível
document.titledocument.titleo texto da aba, em string
document.formsdocument.formsa lista de <form> da página
document.createElementdocument.createElement(nomeDaTag)um elemento novo, vazio

document.body é o append habitual: o elemento criado entra na página e pode ser inspecionado no DevTools.

Node não é element

Na linguagem do dia a dia, "node" virou sinônimo de "elemento", mas na API são coisas diferentes. Node é qualquer pedaço da árvore: um elemento, um texto, um comentário. Element é o tipo de node que tem tag.

Os tipos de node

nodeTypeNome do tipoO que éOnde aparece
1ELEMENT_NODEum elemento com tag<p>, <div>
3TEXT_NODEo texto dentro do elementoo "Olá" de um <h1>
8COMMENT_NODEum comentário do HTML<!-- nota -->
9DOCUMENT_NODEo próprio documenta raiz acima do <html>
11FRAGMENT_NODEum DocumentFragmentagrupar nós antes de inserir

O nome do node está em nodeName: P para elemento, #text para texto. O nome da tag, sem cerquilha e em maiúsculas, está em tagName — e tagName só existe em elemento.

childNodes contra children

childNodes traz tudo, incluindo os nós de texto que o navegador criou sozinho a partir dos espaços e quebras de linha do HTML. children traz só elementos.

PropriedadeAssinaturaO que devolve
el.childNodeselemento.childNodestodos os nós filhos, texto incluído
el.childrenelemento.childrensó os elementos filhos
el.childElementCountelemento.childElementCountquantos elementos filhos existem
el.textContentelemento.textContenttodo o texto da subárvore, junto

Um <div> com três linhas de HTML dentro tem childNodes.length igual a 5 (três elementos e dois nós de texto em branco) e children.length igual a 3. for...of sobre childNodes é a causa clássica de "um item em branco apareceu na lista".

Percorrer a árvore a partir de um elemento

Achou o elemento raiz, a partir dele se chega a qualquer outro sem seletor. A API de navegação é pequena: pai, filhos, irmãos e um closest que sobe.

Vizinhos e parentes

PropriedadeAssinaturaO que devolve
el.parentElementelemento.parentElemento pai, ou null na raiz
el.childrenelemento.childrenos elementos filhos
el.firstElementChildelemento.firstElementChildo primeiro filho, ou null
el.lastElementChildelemento.lastElementChildo último filho, ou null
el.nextElementSiblingelemento.nextElementSiblingo irmão seguinte, ou null
el.closest(seletor)elemento.closest(seletorCSS)o próprio elemento ou o primeiro ancestral que casa
el.matches(seletor)elemento.matches(seletorCSS)true se o elemento casa com o seletor

closest e matches recebem seletor CSS, exatamente o mesmo texto que vai no querySelector. É o mesmo critério de casamento nos dois casos.

Achar o elemento no DevTools

Inspecionar elemento é o atalho para não adivinhar seletor. Com a aba Elements aberta, clicar num elemento da página o destaca na árvore do DOM e o devolve no console como $0. Digitando $0 no console, o navegador entrega o elemento real para inspeção: $0.textContent, $0.className, $0.getAttribute('id').

Um seletor escrito à mão e colado no console (document.querySelector('h1')) é a forma mais rápida de testar se o seletor acha o que o aluno quer, antes de botá-lo no .js.

Exemplo

'use strict';
// ============================================================
// AULA 1 - DOM e a arvore do HTML: document, window e os tipos de node
// ============================================================
// O exemplo monta uma pagina dentro do document e depois navega por ela,
// mostrando que cada elemento do HTML e um objeto do DOM.

// Esta e a pagina que o navegador entregou. O HTML e um arquivo de texto;
// a partir dele o navegador construiu a arvore de objetos abaixo.
document.body.innerHTML = `
  <main id="conteudo">
    <h1 id="titulo">Lista de compras</h1>
    <p class="destaque">Tres itens na semana</p>
    <ul id="lista">
      <li class="item">Arroz</li>
      <li class="item">Feijao</li>
    </ul>
  </main>
`;

// --- A raiz da arvore: html, head e body ---
console.log('=== a raiz ===');
console.log('documentElement:', document.documentElement.tagName);   // HTML
console.log('filhos de <html>:', [...document.documentElement.children].map((f) => f.tagName).join(', '));
console.log('head existe:', document.head.tagName, '| body existe:', document.body.tagName);
console.log('document === window.document:', document === window.document);
console.log('globalThis e a porta padronizada do global:', typeof globalThis.document);

// --- Cada elemento e um objeto ---
const titulo = document.querySelector('#titulo');
console.log('\n=== o objeto <h1> ===');
console.log('tagName:', titulo.tagName, '| nodeName:', titulo.nodeName, '| nodeType:', titulo.nodeType);
console.log('id:', titulo.id, '| textContent:', titulo.textContent);
console.log('o pai do <h1> e o:', titulo.parentElement.tagName + '#' + titulo.parentElement.id);

// --- Node e element sao coisas diferentes ---
console.log('\n=== node contra element ===');
const lista = document.querySelector('#lista');
console.log('tagName so existe em elemento; no texto da lista vale:', lista.firstChild.nodeName);
console.log('childNodes (elementos + os textos em branco do HTML):', lista.childNodes.length);
console.log('children (so elementos):', lista.children.length);

// Um node de texto criado na mao e o tipo 3. Um elemento e o tipo 1.
const texto = document.createTextNode('So um texto');
console.log('createTextNode -> nodeType:', texto.nodeType, '| nodeName:', texto.nodeName);

// --- A tabela de tipos que a aula mostra ---
const tipos = {
  1: 'ELEMENT_NODE (um <p>, um <div>)',
  3: 'TEXT_NODE (o texto dentro do elemento)',
  8: 'COMMENT_NODE (um comentario do HTML)',
  9: 'DOCUMENT_NODE (o proprio document)',
  11: 'FRAGMENT_NODE (um DocumentFragment)',
};
for (const [codigo, nome] of Object.entries(tipos)) {
  console.log(`nodeType ${codigo}: ${nome}`);
}

// --- Navegar pela arvore a partir de um elemento ---
console.log('\n=== vizinhos e parentes ===');
const primeiro = lista.firstElementChild;
const segundo = lista.lastElementChild;
console.log('primeiro filho:', primeiro.textContent, '| ultimo filho:', segundo.textContent);
console.log('proximo do primeiro e o segundo:', primeiro.nextElementSibling === segundo);
console.log('anterior do segundo e o primeiro:', segundo.previousElementSibling === primeiro);
console.log('closest("ul") acha o proprio pai:', primeiro.closest('ul') === lista);
console.log('matches(".item"):', primeiro.matches('li.item'), '| matches(".outra"):', primeiro.matches('.outra'));
console.log('o pai do pai:', primeiro.parentElement.parentElement.tagName + '#' + primeiro.parentElement.parentElement.id);

Saída real

=== a raiz ===
documentElement: HTML
filhos de <html>: HEAD, BODY
head existe: HEAD | body existe: BODY
document === window.document: true
globalThis e a porta padronizada do global: object

=== o objeto <h1> ===
tagName: H1 | nodeName: H1 | nodeType: 1
id: titulo | textContent: Lista de compras
o pai do <h1> e o: MAIN#conteudo

=== node contra element ===
tagName so existe em elemento; no texto da lista vale: #text
childNodes (elementos + os textos em branco do HTML): 5
children (so elementos): 2
createTextNode -> nodeType: 3 | nodeName: #text
nodeType 1: ELEMENT_NODE (um <p>, um <div>)
nodeType 3: TEXT_NODE (o texto dentro do elemento)
nodeType 8: COMMENT_NODE (um comentario do HTML)
nodeType 9: DOCUMENT_NODE (o proprio document)
nodeType 11: FRAGMENT_NODE (um DocumentFragment)

=== vizinhos e parentes ===
primeiro filho: Arroz | ultimo filho: Feijao
proximo do primeiro e o segundo: true
anterior do segundo e o primeiro: true
closest("ul") acha o proprio pai: true
matches(".item"): true | matches(".outra"): false
o pai do pai: MAIN#conteudo
Aula 2

querySelector e alterar o que esta na tela

Buscar elemento: seletor CSS

querySelector recebe um seletor CSS e devolve o primeiro elemento que casa com ele. querySelectorAll recebe o mesmo texto e devolve todos. Os dois se chamam a partir de qualquer elemento, e o mais usado é o document, que procura na página inteira.

Como se usa

const titulo = document.querySelector('h1');        // primeiro <h1>
const item = document.querySelector('#titulo');     // elemento com id="titulo"
const todos = document.querySelectorAll('li.campo'); // todos os <li class="campo">

O argumento é uma string. '#titulo' é seletor de id, '.campo' é seletor de classe, 'li' é seletor de tag, '[data-id]' é seletor de atributo, e a combinação é livre: 'form label.campo' acha a label de classe campo dentro de um form.

As formas de seletor que resolvem o caso comum

SeletorAssinaturaO que acha
tagquerySelector('h1')o primeiro h1 na ordem do documento
#idquerySelector('#titulo')o elemento com aquele id
.classequerySelector('.destaque')o primeiro com aquela classe
[atributo]querySelector('[data-id]')o elemento que tem o atributo
[atributo="v"]querySelector('[data-id="7"]')o elemento com o atributo valendo "7"
p, h2querySelector('p, h2')o primeiro que casa com qualquer da lista
div > pquerySelector('div > p')filho direto, não neto
.a .bquerySelector('lista .item')descendente em qualquer nível

O seletor de atributo, com os colchetes, é o único que alcança o que não tem nem tag nem classe: o data-*, o aria-*, o type de um campo.

O que devolve quando não acha

querySelector devolve null quando nada casa. null não tem propriedade: a linha seguinte quebra com TypeError: Cannot read properties of null.

SituaçãoquerySelectorquerySelectorAll
achou um elementoo elementoNodeList com 1 item
achou várioso primeiro da listaNodeList com todos
não achou nadanullNodeList vazia, com .length 0

O NodeList não é array: tem .length e percorre com for...of, mas não tem push e não tem map. Para transformar em array de verdade, o embrulho é [...document.querySelectorAll('li')].

Confundir os dois retornos gera o bug mais comum do dia: querySelectorAll nunca devolve null, então o if (itens) sempre passa e o for...of itera uma lista vazia sem avisar nada.

As formas antigas de buscar

getElementById e getElementsByClassName existem há mais tempo e ainda funcionam. A diferença de contrato é a que mais incomoda: os dois devolvem uma coleção viva, que se refaz sozinha quando a página muda, e nunca null.

MétodoAssinaturaDevolve
querySelectorquerySelector(seletorCSS)um elemento ou null
querySelectorAllquerySelectorAll(seletorCSS)NodeList estática, pode ser vazia
getElementByIddocument.getElementById(id)um elemento ou null
getElementsByClassNamedocument.getElementsByClassName(nome)HTMLCollection viva, nunca vazia
getElementsByTagNamedocument.getElementsByTagName(tag)HTMLCollection viva

Nem sempre é preciso seletor. Quando o que se quer é simplesmente o primeiro filho, o firstElementChild faz isso sem escrever texto de seletor: pai.firstElementChild devolve o primeiro filho que é elemento, e null se não houver nenhum.

A distinção que importa é com o firstChild: o firstElementChild pula nós de texto — os espaços e quebras de linha entre as tags — e devolve só elemento. O firstChild devolve o nó de texto em branco se ele vier primeiro, e é por isso que firstChild.nextSibling aparece em código antigo e quebra quando alguém muda a formatação do HTML.

ul.firstElementChild;         // o primeiro <li>
ul.firstElementChild.className;
ul.firstElementChild === null;   // true se a lista está vazia

Em código novo, querySelector cobre os dois casos: #id faz o papel do getElementById e devolve null em vez de coleção.

Ler e trocar o texto

Texto é a propriedade mais lida da página. São quatro propriedades, e a diferença entre elas decide se o conteúdo aparece com as tags ou não.

O que cada propriedade devolve

PropriedadeAssinaturaO que faz
textContentel.textContent = 'novo'o texto puro, ignora as tags e o CSS
innerHTMLel.innerHTML = '<b>oi</b>'o HTML dentro do elemento, com as tags
innerTextel.innerText = 'novo'o texto como aparece na tela, respeita o CSS
valuecampo.value = 'Ana'o valor de input, textarea e select

textContent apaga as tags ao ser lido e coloca texto puro ao ser escrito. innerHTML interpreta as tags: ler devolve a string de HTML, escrever monta elementos de verdade.

innerText é o intermediário: devolve o texto visível, então um elemento escondido por CSS não aparece nele. Ele também é mais lento, porque precisa recalcular o desenho da página. Em código que roda a cada tecla, textContent é o certo.

Trocar o texto de uma lista

document.querySelector('#total').textContent = '3 itens';
document.querySelector('#vazio').innerHTML = '<strong>Nada</strong> aqui';

Classes e atributos

Classe é o que o CSS usa para pintar, e por isso é o atributo que o JavaScript mais mexe. Ela tem uma API própria, a classList, que evita reescrever a string inteira e estragar as classes que não são suas.

classList

MétodoAssinaturaEfeito
addel.classList.add('ativa')acrescenta a classe, repete não duplica
removeel.classList.remove('ativa')tira a classe
toggleel.classList.toggle('ativa')põe se não está, tira se está
containsel.classList.contains('ativa')true se a classe está lá
replaceel.classList.replace('velha', 'nova')troca uma classe existente
valueel.classList.valuea string de classes, para ler

el.className = 'a b' reescreve a lista toda e apaga as classes que o CSS do projeto esperava. classList mexe numa só.

getAttribute e setAttribute

Atributo é o que está escrito no HTML. serve para os casos em que não existe propriedade com nome próprio — data-*, aria-*, href, type, disabled.

MétodoAssinaturaEfeito
getAttributeel.getAttribute('nome')o valor do atributo, ou null
setAttributeel.setAttribute('nome', 'valor')cria o atributo ou troca o valor
hasAttributeel.hasAttribute('nome')true se o atributo existe
removeAttributeel.removeAttribute('nome')apaga o atributo
datasetel.dataset.nomeUsuarioo mesmo atributo, em notação de ponto

Atributo e propriedade não são a mesma coisa: getAttribute('value') devolve o que está escrito no HTML, enquanto campo.value devolve o que o usuário digitou agora. Depois que alguém digita, os dois divergem.

O dataset é a porta de entrada do data-*: data-id-usuario="7" no HTML vira el.dataset.idUsuario.

classList e className leem sempre a lista de classes em class, e id é uma propriedade direta: el.id = 'novo'. Nenhum dos dois é atributo de verdade, então não passam por getAttribute.

Exemplo

'use strict';
// ============================================================
// AULA 2 - querySelector e alterar o que esta na tela
// ============================================================
// O exemplo procura elementos por seletor CSS e depois mexe no texto,
// nas classes e nos atributos, comparando as formas de ler e de trocar.

document.body.innerHTML = `
  <h1 id="titulo" data-pagina="1">Lista de compras</h1>
  <p class="destaque aviso">Arroz, feijao e cafe</p>
  <ul id="lista">
    <li class="item" data-id="1">Arroz</li>
    <li class="item" data-id="2">Feijao</li>
    <li class="item" data-id="3">Cafe</li>
  </ul>
`;

// --- As formas de seletor ---
console.log('=== seletor CSS ===');
console.log('por tag    ->', document.querySelector('h1').textContent);
console.log('por id     ->', document.querySelector('#titulo').textContent);
console.log('por classe ->', document.querySelector('.destaque').textContent);
console.log('por atributo ->', document.querySelector('[data-id="2"]').textContent);
console.log('combinado  ->', document.querySelector('ul li.item').textContent);
console.log('descendente->', document.querySelector('ul .item').textContent);
console.log('filho direto ->', document.querySelector('ul > li').textContent);
console.log('lista de seletores ->', document.querySelector('h1, .aviso').textContent);

// --- querySelector devolve um; querySelectorAll devolve todos ---
const itens = document.querySelectorAll('li.item');
console.log('\n=== querySelectorAll ===');
console.log('quantidade:', itens.length, '| e NodeList, nao array:', Array.isArray(itens));
console.log('o primeiro item:', itens[0].textContent);
for (const item of itens) {
  console.log('  item', item.dataset.id, '=', item.textContent);
}

// --- O que acontece quando o seletor nao acha nada ---
console.log('\n=== quando nao acha ===');
const naoExiste = document.querySelector('#nao-existe');
console.log('querySelector devolve:', naoExiste);
console.log('querySelectorAll devolve lista com length:', document.querySelectorAll('.nao-existe').length);
// E por isso que o if precisa do guarda: null nao tem propriedade.
if (naoExiste) {
  console.log(naoExiste.textContent);
} else {
  console.log('sem guarda, a linha acima quebraria com TypeError');
}

// --- As formas antigas: colecao viva, nunca null ---
console.log('\n=== getElementById e companhia ===');
console.log('getElementById("#lista"):', document.getElementById('lista').tagName);
console.log('getElementsByClassName("item").length:', document.getElementsByClassName('item').length);

// --- Ler e trocar texto ---
console.log('\n=== texto ===');
const titulo = document.querySelector('#titulo');
console.log('textContent:', titulo.textContent);
console.log('innerHTML:', titulo.innerHTML);
titulo.textContent = 'Compras da semana';
console.log('depois de textContent =', titulo.textContent, '| innerHTML:', titulo.innerHTML);

// textContent apaga as tags ao escrever; innerHTML interpreta o que veio.
const lista = document.querySelector('#lista');
console.log('textContent da lista junta todo o texto:', JSON.stringify(lista.textContent));

const aviso = document.querySelector('.aviso');
aviso.innerHTML = '<strong>Atencao:</strong> falta o cafe';
console.log('depois de innerHTML, innerHTML:', aviso.innerHTML);
console.log('e o textContent do mesmo elemento:', JSON.stringify(aviso.textContent));

// --- classList mexe numa classe sem apagar as outras ---
console.log('\n=== classList ===');
const item = document.querySelector('li[data-id="2"]');
console.log('className inicial:', item.className);
item.classList.add('comprado');
item.classList.remove('item');
console.log('depois de add e remove:', item.className);
console.log('contains("comprado"):', item.classList.contains('comprado'));
item.classList.toggle('urgente');
console.log('depois do toggle:', item.className);
item.classList.replace('comprado', 'pago');
console.log('depois do replace:', item.className);
// Escrever className inteiro apaga tudo que o CSS do projeto esperava.
item.className = 'zerado';
console.log('className = "zerado" apagou o resto:', item.className);

// --- getAttribute, setAttribute e dataset ---
console.log('\n=== atributos ===');
const cafe = document.querySelector('li[data-id="3"]');
console.log('getAttribute("data-id"):', cafe.getAttribute('data-id'));
console.log('dataset.id:', cafe.dataset.id);
cafe.setAttribute('data-status', 'pendente');
console.log('depois do setAttribute:', cafe.outerHTML);
console.log('hasAttribute("data-status"):', cafe.hasAttribute('data-status'));
cafe.removeAttribute('data-status');
console.log('depois do removeAttribute:', cafe.hasAttribute('data-status'));

// --- value e atributo sao coisas diferentes ---
console.log('\n=== value ===');
const campo = document.createElement('input');
campo.setAttribute('value', 'Ana');
document.body.appendChild(campo);
console.log('value:', campo.value);
campo.value = 'Bia Souza';
console.log('depois de digitar, value:', campo.value);
console.log('mas o atributo continua:', campo.getAttribute('value'));

Saída real

=== seletor CSS ===
por tag    -> Lista de compras
por id     -> Lista de compras
por classe -> Arroz, feijao e cafe
por atributo -> Feijao
combinado  -> Arroz
descendente-> Arroz
filho direto -> Arroz
lista de seletores -> Lista de compras

=== querySelectorAll ===
quantidade: 3 | e NodeList, nao array: false
o primeiro item: Arroz
  item 1 = Arroz
  item 2 = Feijao
  item 3 = Cafe

=== quando nao acha ===
querySelector devolve: null
querySelectorAll devolve lista com length: 0
sem guarda, a linha acima quebraria com TypeError

=== getElementById e companhia ===
getElementById("#lista"): UL
getElementsByClassName("item").length: 3

=== texto ===
textContent: Lista de compras
innerHTML: Lista de compras
depois de textContent = Compras da semana | innerHTML: Compras da semana
textContent da lista junta todo o texto: "\n    Arroz\n    Feijao\n    Cafe\n  "
depois de innerHTML, innerHTML: <strong>Atencao:</strong> falta o cafe
e o textContent do mesmo elemento: "Atencao: falta o cafe"

=== classList ===
className inicial: item
depois de add e remove: comprado
contains("comprado"): true
depois do toggle: comprado urgente
depois do replace: pago urgente
className = "zerado" apagou o resto: zerado

=== atributos ===
getAttribute("data-id"): 3
dataset.id: 3
depois do setAttribute: <li class="item" data-id="3" data-status="pendente">Cafe</li>
hasAttribute("data-status"): true
depois do removeAttribute: false

=== value ===
value: Ana
depois de digitar, value: Bia Souza
mas o atributo continua: Ana