W3docs

JavaScript — Percorrendo o DOM

Percorrer o DOM é uma habilidade fundamental para desenvolvedores web. Domine a navegação pelo DOM para manipular páginas dinamicamente.

Percorrer o DOM (Document Object Model) é uma habilidade fundamental para desenvolvedores web que usam JavaScript. Dominar a navegação pelo DOM permitirá que você manipule páginas web de forma dinâmica, criando experiências de usuário interativas e responsivas. Este guia fornecerá explicações detalhadas e múltiplos exemplos de código para ajudá-lo a se tornar proficiente na navegação pelo DOM.

Introdução à Navegação pelo DOM

O DOM representa a estrutura de uma página web como uma árvore de nós. Cada nó corresponde a um elemento, um trecho de texto ou um comentário na página. Percorrer o DOM significa mover-se de um nó para outro — subindo para um pai, descendo para filhos ou lateralmente para irmãos — para ler ou alterar elementos em relação a um ponto de partida.

Por que navegar em vez de apenas selecionar? Muitas vezes você começa a partir de um elemento que já possui (por exemplo, o botão que um usuário acabou de clicar) e precisa alcançar um elemento relacionado cujo id ou classe exata você não conhece antecipadamente — seu contêiner, o próximo item de uma lista ou cada resposta aninhada abaixo dele. A navegação expressa "o elemento ao lado / dentro / em torno deste."

Este guia cobre as propriedades de relacionamento que você usa com mais frequência:

DireçãoPropriedade apenas de elementosPropriedade que inclui tudo
Abaixo (filhos)children, firstElementChild, lastElementChildchildNodes, firstChild, lastChild
Acima (pai)parentElementparentNode
Lateral (irmãos)nextElementSibling, previousElementSiblingnextSibling, previousSibling

A coluna da esquerda ignora nós de texto e comentários, por isso é quase sempre o que você deseja. A coluna da direita inclui nós de texto com espaço em branco entre as tags, o que é uma fonte comum de bugs.

Para encontrar elementos em qualquer lugar no documento (em vez de em relação a um nó), veja Buscando: getElement* e querySelector e Selecionando Elementos DOM.

Entendendo a Árvore do DOM

Antes de mergulhar nos métodos de navegação, é útil visualizar a árvore do DOM. Aqui está um documento HTML simples para ilustrar:

<!DOCTYPE html>
<html>
<head>
    <title>DOM Traversal Example</title>
</head>
<body>
    <div id="container">
        <p class="text">Hello, World!</p>
        <ul>
            <li>Item 1</li>
            <li>Item 2</li>
            <li>Item 3</li>
        </ul>
    </div>
</body>
</html>

Neste documento, o elemento <body> contém um <div> com um id de container, que por sua vez contém um elemento <p> e um <ul> com filhos <li>. Para saber como o navegador classifica cada parte (elemento, texto, comentário), veja Entendendo os Nós do DOM.

Métodos Básicos de Navegação

Acessando Nós Filhos

Imagine que você tem um blog com vários posts e cada post tem comentários. Você quer contar os comentários de um post específico.

<!DOCTYPE html>
<html>
<head>
    <title>Accessing Child Nodes</title>
</head>
<body>
    <div id="blog-post">
        <h2>Blog Post Title</h2>
        <p>Some interesting content...</p>
        <div class="comments">
            <p>Comment 1</p>
            <p>Comment 2</p>
            <p>Comment 3</p>
        </div>
    </div>

    <script>
        const commentsContainer = document.querySelector('.comments');
        const comments = commentsContainer.children; // Only includes element nodes

        // Display the number of comments
        console.log(`Number of comments: ${comments.length}`);
    </script>
</body>
</html>

Este código seleciona o <div> com a classe "comments" e exibe o número de elementos de comentário dentro dele. Observação: children retorna apenas nós de elemento, enquanto childNodes inclui nós de texto e comentários. Para navegação apenas de elementos, prefira children.

Imagine que você tem uma lista de itens em um carrinho de compras e quer encontrar o elemento contêiner de um item específico.

<!DOCTYPE html>
<html>
<head>
    <title>Navigating to Parent Nodes</title>
</head>
<body>
    <div id="shopping-cart">
        <ul>
            <li>Item 1</li>
            <li>Item 2</li>
            <li>Item 3</li>
        </ul>
    </div>

    <script>
        const cartItem = document.querySelector('li');
        const parent = cartItem.parentNode;

        // Display the parent node
        console.log(`The parent of the first cart item is a: ${parent.tagName}`);
    </script>
</body>
</html>

Este código seleciona o primeiro elemento <li> e exibe o nome da tag de seu nó pai. Para navegação apenas de elementos, parentElement é frequentemente preferido em relação a parentNode, pois ignora nós de texto e retorna null se o pai não for um elemento.

Nós Irmãos

Imagine que você tem uma lista de tarefas onde pode marcar tarefas como concluídas e então passar para a próxima tarefa.

<!DOCTYPE html>
<html>
<head>
    <title>Task List Navigation</title>
    <style>
        .task {
            margin: 10px;
            padding: 10px;
            border: 1px solid #ccc;
        }
        .completed {
            text-decoration: line-through;
            color: gray;
        }
    </style>
</head>
<body>
    <div class="task-list">
        <div class="task">
            <p>Task 1: Do the laundry</p>
            <button class="complete-task">Complete Task</button>
        </div>
        <div class="task">
            <p>Task 2: Buy groceries</p>
            <button class="complete-task">Complete Task</button>
        </div>
        <div class="task">
            <p>Task 3: Clean the house</p>
            <button class="complete-task">Complete Task</button>
        </div>
    </div>

    <script>
        document.querySelectorAll('.complete-task').forEach(button => {
            button.addEventListener('click', () => {
                const task = button.parentElement;
                task.classList.add('completed');
                button.disabled = true;

                const nextTask = task.nextElementSibling;
                if (nextTask) {
                    console.log(`Next task: ${nextTask.querySelector('p').textContent}`);
                } else {
                    console.log('No more tasks available');
                }
            });
        });
    </script>
</body>
</html>

Este código fornece uma lista de tarefas onde cada tarefa tem um botão "Complete Task". Quando uma tarefa é marcada como concluída, o texto é riscado e o botão é desativado. Ele também exibe a descrição da próxima tarefa. Se não houver mais tarefas, indica que não há mais tarefas disponíveis. Da mesma forma, previousElementSibling e nextElementSibling ignoram nós de texto, tornando-os mais seguros para navegação apenas de elementos do que previousSibling e nextSibling.

Técnicas Avançadas de Navegação

Encontrando Elementos por Classe ou Tag

Imagine que você está criando um painel que lista todos os usuários e quer encontrar e contar todos os elementos de usuário.

<!DOCTYPE html>
<html>
<head>
    <title>Finding Elements by Class or Tag</title>
</head>
<body>
    <div class="user">User 1</div>
    <div class="user">User 2</div>
    <div class="user">User 3</div>

    <script>
        const users = document.getElementsByClassName('user');

        // Display the number of users
        console.log(`Number of users: ${users.length}`);
    </script>
</body>
</html>

Este código conta e exibe o número de elementos com a classe user. Um detalhe sutil, mas importante: getElementsByClassName (e getElementsByTagName) retorna um HTMLCollection dinâmico — ele é atualizado automaticamente conforme o DOM muda. Se você adicionar um quarto elemento .user posteriormente, users.length se tornará 4 sem precisar de nova consulta. querySelectorAll, por outro lado, retorna um NodeList estático que é um instantâneo feito no momento da chamada. Compare os dois em Buscando: getElement* e querySelector.

Métodos Query Selector

Imagine que você tem um site de notícias e quer destacar todas as manchetes.

<!DOCTYPE html>
<html>
<head>
    <title>Query Selector Methods</title>
</head>
<body>
    <div id="news">
        <h1 class="headline">Headline 1</h1>
        <h1 class="headline">Headline 2</h1>
        <h1 class="headline">Headline 3</h1>
    </div>

    <script>
        const headlines = document.querySelectorAll('.headline');

        // Highlight all headlines
        headlines.forEach(headline => {
            headline.style.color = 'red';
        });

        // Display the number of headlines
        console.log(`Number of headlines: ${headlines.length}`);
    </script>
</body>
</html>

Este código seleciona todos os elementos com a classe headline, muda sua cor para vermelho e exibe a contagem desses elementos.

Vamos criar um exemplo do mundo real para navegação recursiva. Usaremos um sistema de comentários aninhados como exemplo, onde cada comentário pode ter respostas.

<!DOCTYPE html>
<html>
<head>
    <title>Recursive Traversal</title>
    <style>
        .comment {
            margin: 10px;
            padding: 10px;
            border: 1px solid #ccc;
        }
        .reply {
            margin-left: 20px;
            border-left: 2px solid #aaa;
        }
    </style>
</head>
<body>
    <div class="comments">
        <div class="comment">
            <p>Comment 1</p>
            <div class="reply">
                <p>Reply 1-1</p>
                <div class="reply">
                    <p>Reply 1-1-1</p>
                </div>
            </div>
            <div class="reply">
                <p>Reply 1-2</p>
            </div>
        </div>
        <div class="comment">
            <p>Comment 2</p>
            <div class="reply">
                <p>Reply 2-1</p>
            </div>
        </div>
    </div>

    <script>
        function traverseComments(node) {
            if (!node) return; // Guard against null/undefined

            if (node.nodeType === Node.ELEMENT_NODE && node.classList.contains('comment')) {
                console.log(`Comment: ${node.querySelector('p').textContent}`);
            }

            if (node.nodeType === Node.ELEMENT_NODE && node.classList.contains('reply')) {
                console.log(`Reply: ${node.querySelector('p').textContent}`);
            }

            for (let i = 0; i < node.childNodes.length; i++) {
                traverseComments(node.childNodes[i]);
            }
        }

        traverseComments(document.querySelector('.comments'));
    </script>
</body>
</html>

Este código representa um sistema de comentários aninhados com comentários e respostas. A função traverseComments percorre recursivamente cada comentário e resposta, exibindo seu conteúdo de texto. A estrutura aninhada permite respostas a respostas, demonstrando um caso de uso real de navegação recursiva. Sempre inclua uma verificação de null/undefined no início de funções recursivas para evitar erros quando o seletor inicial não retorna nada.

Exemplos Práticos

Estes exemplos combinam a navegação pelo DOM com técnicas comuns de manipulação para demonstrar fluxos de trabalho do mundo real.

Criando uma Lista de Tarefas Dinâmica

Imagine que você tem uma lista de tarefas onde os usuários podem adicionar novas tarefas.

<!DOCTYPE html>
<html>
<head>
    <title>Dynamic To-Do List</title>
    <style>
        .info { color: darkgreen; }
    </style>
</head>
<body>
    <div id="todo-list">
        <h2>To-Do List</h2>
        <ul id="tasks">
            <li>Task 1</li>
            <li>Task 2</li>
        </ul>
        <input type="text" id="task-input" placeholder="Add a new task" />
        <button id="add-button">Add Task</button>
    </div>

    <script>
        const tasks = document.getElementById('tasks');
        const input = document.getElementById('task-input');
        const button = document.getElementById('add-button');

        button.addEventListener('click', () => {
            const newTask = input.value.trim();
            if (newTask) {
                const li = document.createElement('li');
                li.textContent = newTask;
                tasks.appendChild(li);
                input.value = '';
                console.log('Added new task to the to-do list');
            }
        });
    </script>
</body>
</html>

Este código permite que os usuários adicionem novas tarefas a uma lista de tarefas inserindo texto em um campo de entrada e clicando em um botão.

Atualizando Atributos de Elementos

Imagine que você tem uma lista de produtos e quer marcar produtos como "favorito" quando eles são clicados.

<!DOCTYPE html>
<html>
<head>
    <title>Updating Element Attributes</title>
    <style>
        .favorite { font-weight: bold; color: gold; }
        .info { color: darkblue; }
    </style>
</head>
<body>
<h4>Click on the list item below to see the result!</h4>
    <ul id="product-list">
        <li>Product 1</li>
        <li>Product 2</li>
        <li>Product 3</li>
    </ul>

    <script>
        const productList = document.getElementById('product-list');

        productList.addEventListener('click', (event) => {
            if (event.target.tagName === 'LI') {
                event.target.classList.toggle('favorite');
                console.log(`Toggled favorite status for: ${event.target.textContent}`);
            }
        });
    </script>
</body>
</html>

Este código permite que os usuários marquem produtos como "favorito" clicando neles, alterando sua aparência usando uma classe favorite.

Informação

Minimize o acesso ao DOM para melhorar o desempenho. Agrupe as manipulações do DOM para reduzir reflows e repaints.

Armadilhas Comuns

Algumas armadilhas são responsáveis pela maioria dos bugs de navegação pelo DOM:

  • Nós de texto com espaço em branco. firstChild, nextSibling e childNodes contam o espaço em branco e as quebras de linha entre as tags como nós de texto. O primeiro filho de um <ul> escrito em várias linhas geralmente é um nó de texto, não o primeiro <li>. Use as versões apenas de elementos (firstElementChild, nextElementSibling, children) a menos que você precise especificamente de nós de texto.
  • Esquecer que a navegação pode retornar null. parentElement, nextElementSibling e similares retornam null nas extremidades da árvore (o último irmão não tem nextElementSibling). Sempre verifique antes de chamar um método no resultado, como o exemplo do irmão acima faz com if (nextTask).
  • Tratar coleções como arrays. children, childNodes e getElementsByClassName retornam coleções, não arrays reais. HTMLCollection não tem forEach. Converta com Array.from(collection) ou [...collection] quando precisar de métodos de array como map ou filter. (NodeList de querySelectorAll tem forEach, mas não map.)
  • Iterar sobre uma coleção dinâmica enquanto a modifica. Como getElementsByClassName é dinâmico, adicionar ou remover elementos correspondentes dentro de um laço for sobre ele pode pular itens ou criar um laço infinito. Tire um instantâneo primeiro com Array.from(...) se você planeja fazer mutações durante a iteração.

Para aprofundar o entendimento de como os nós são tipados e como seu conteúdo parece, leia Propriedades do Nó: tipo, tag e conteúdo. Para responder a ações do usuário enquanto navega, veja Manipulação de Eventos no DOM.

Conclusão

Dominar a navegação pelo DOM é essencial para criar aplicações web dinâmicas e interativas. Ao entender e utilizar os vários métodos e técnicas para navegar e manipular o DOM, você pode melhorar as experiências dos usuários e aprimorar a funcionalidade de seus projetos web.

Prática

Prática
Qual dos métodos a seguir pode ser usado para percorrer o DOM em JavaScript?
Qual dos métodos a seguir pode ser usado para percorrer o DOM em JavaScript?
Was this page helpful?