W3docs

JavaScript - Attraversamento del DOM

Impara ad attraversare il DOM con JavaScript per manipolare le pagine web in modo dinamico e creare esperienze utente interattive e reattive.

Attraversare il DOM (Document Object Model) è una competenza fondamentale per gli sviluppatori web che utilizzano JavaScript. Padroneggiare l'attraversamento del DOM ti permetterà di manipolare le pagine web in modo dinamico, creando esperienze utente interattive e reattive. Questa guida ti fornirà spiegazioni dettagliate e numerosi esempi di codice per aiutarti a diventare esperto nell'attraversamento del DOM.

Introduzione all'attraversamento del DOM

Il DOM rappresenta la struttura di una pagina web come un albero di nodi. Ogni nodo corrisponde a un elemento, a un pezzo di testo o a un commento nella pagina. Attraversare il DOM significa spostarsi da un nodo all'altro — verso l'alto fino al genitore, verso il basso fino ai figli, o lateralmente verso i fratelli — per leggere o modificare elementi rispetto a un punto di partenza.

Perché attraversare invece di selezionare semplicemente? Spesso si parte da un elemento che già si possiede (per esempio, il pulsante appena cliccato dall'utente) e si deve raggiungere un elemento correlato di cui non si conosce in anticipo l'id o la classe esatta — il suo contenitore, l'elemento successivo in un elenco, o ogni risposta nidificata sotto di esso. L'attraversamento esprime "l'elemento accanto a / dentro / intorno a questo."

Questa guida copre le proprietà di relazione più utilizzate:

DirezioneProprietà solo-elementoProprietà tutto-incluso
Verso il basso (figli)children, firstElementChild, lastElementChildchildNodes, firstChild, lastChild
Verso l'alto (genitore)parentElementparentNode
Lateralmente (fratelli)nextElementSibling, previousElementSiblingnextSibling, previousSibling

La colonna sinistra salta i nodi di testo e di commento, quindi è quasi sempre quella che si vuole usare. La colonna destra include i nodi di testo costituiti da spazi bianchi tra i tag, che sono una fonte comune di bug.

Per trovare elementi in qualsiasi punto del documento (anziché rispetto a un nodo), vedi Ricerca: getElement* e querySelector e Selezione degli elementi DOM.

Comprendere l'albero del DOM

Prima di immergersi nei metodi di attraversamento, è utile visualizzare l'albero del DOM. Ecco un semplice documento HTML per illustrarlo:

<!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>

In questo documento, l'elemento <body> contiene un <div> con un id pari a container, che a sua volta contiene un elemento <p> e un <ul> con figli <li>. Per scoprire come il browser classifica ogni elemento (elemento, testo, commento), vedi Comprendere i nodi del DOM.

Metodi di attraversamento di base

Accesso ai nodi figli

Immagina di avere un blog con più post, ognuno con i relativi commenti. Vuoi contare i commenti per un post specifico.

<!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>

Questo codice seleziona il <div> con la classe "comments" e visualizza il numero di elementi commento al suo interno. Nota: children restituisce solo i nodi elemento, mentre childNodes include anche i nodi di testo e di commento. Per l'attraversamento solo degli elementi, preferisci children.

Immagina di avere un elenco di articoli in un carrello della spesa e di voler trovare l'elemento contenitore di un articolo specifico.

<!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>

Questo codice seleziona il primo elemento <li> e visualizza il nome del tag del suo nodo genitore. Per la navigazione solo tra elementi, parentElement è spesso preferito a parentNode in quanto salta i nodi di testo e restituisce null se il genitore non è un elemento.

Nodi fratelli

Immagina di avere un elenco di attività in cui puoi segnare le attività come completate e passare poi a quella successiva.

<!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>

Questo codice fornisce un elenco di attività in cui ogni attività ha un pulsante "Complete Task". Quando un'attività viene contrassegnata come completata, il testo viene barrato e il pulsante viene disabilitato. Viene inoltre visualizzata la descrizione dell'attività successiva. Se non ci sono altre attività, viene indicato che non ne sono disponibili. Analogamente, previousElementSibling e nextElementSibling saltano i nodi di testo, rendendoli più sicuri per l'attraversamento solo tra elementi rispetto a previousSibling e nextSibling.

Tecniche di attraversamento avanzate

Trovare elementi per classe o tag

Immagina di stare creando una dashboard che elenca tutti gli utenti e di voler trovare e contare tutti gli elementi utente.

<!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>

Questo codice conta e visualizza il numero di elementi con la classe user. Un dettaglio sottile ma importante: getElementsByClassName (e getElementsByTagName) restituisce un'HTMLCollection live — si aggiorna automaticamente man mano che il DOM cambia. Se aggiungi un quarto elemento .user in seguito, users.length diventa 4 senza dover eseguire una nuova query. querySelectorAll, al contrario, restituisce una NodeList statica che è un'istantanea scattata al momento della chiamata. Confronta i due in Ricerca: getElement* e querySelector.

Metodi Query Selector

Immagina di avere un sito di notizie e di voler evidenziare tutti i titoli.

<!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>

Questo codice seleziona tutti gli elementi con la classe headline, ne cambia il colore in rosso e visualizza il loro conteggio.

Attraversamento tramite funzioni ricorsive

Creiamo un esempio pratico per l'attraversamento ricorsivo. Utilizzeremo un sistema di commenti nidificati come esempio, in cui ogni commento può avere risposte.

<!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>

Questo codice rappresenta un sistema di commenti nidificati con commenti e risposte. La funzione traverseComments attraversa ricorsivamente ogni commento e risposta, visualizzandone il contenuto testuale. La struttura nidificata consente risposte alle risposte, dimostrando un caso d'uso reale dell'attraversamento ricorsivo. Includi sempre un controllo null/undefined all'inizio delle funzioni ricorsive per prevenire errori quando il selettore iniziale non restituisce nulla.

Esempi pratici

Questi esempi combinano l'attraversamento del DOM con tecniche di manipolazione comuni per illustrare flussi di lavoro del mondo reale.

Creazione di una lista To-Do dinamica

Immagina di avere una lista di cose da fare in cui gli utenti possono aggiungere nuove attività.

<!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>

Questo codice consente agli utenti di aggiungere nuove attività a una lista di cose da fare inserendo del testo in un campo di input e facendo clic su un pulsante.

Aggiornamento degli attributi degli elementi

Immagina di avere un elenco di prodotti e di voler contrassegnare i prodotti come "preferiti" quando vengono cliccati.

<!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>

Questo codice consente agli utenti di contrassegnare i prodotti come "preferiti" cliccandoci sopra, modificandone l'aspetto tramite una classe favorite.

Informazione

Riduci al minimo gli accessi al DOM per migliorare le prestazioni. Raggruppa le manipolazioni del DOM per ridurre i reflow e i repaint.

Errori comuni

Alcune trappole sono responsabili della maggior parte dei bug nell'attraversamento del DOM:

  • Nodi di testo con spazi bianchi. firstChild, nextSibling e childNodes contano gli spazi bianchi e le interruzioni di riga tra i tag come nodi di testo. Il primo figlio di un <ul> scritto su più righe è di solito un nodo di testo, non il primo <li>. Usa le versioni solo-elemento (firstElementChild, nextElementSibling, children) a meno che tu non abbia specificamente bisogno dei nodi di testo.
  • Dimenticare che l'attraversamento può restituire null. parentElement, nextElementSibling e simili restituiscono null ai bordi dell'albero (l'ultimo fratello non ha nextElementSibling). Verifica sempre prima di chiamare un metodo sul risultato, come fa l'esempio dei fratelli con if (nextTask).
  • Trattare le collezioni come array. children, childNodes e getElementsByClassName restituiscono collezioni, non array veri e propri. HTMLCollection non ha forEach. Converti con Array.from(collection) o [...collection] quando hai bisogno di metodi array come map o filter. (La NodeList di querySelectorAll ha forEach, ma non map.)
  • Iterare su una collezione live mentre la si modifica. Poiché getElementsByClassName è live, aggiungere o rimuovere elementi corrispondenti all'interno di un ciclo for su di essa può saltare elementi o andare in loop all'infinito. Crea prima un'istantanea con Array.from(...) se prevedi di mutare durante l'iterazione.

Per approfondire come vengono tipizzati i nodi e come appare il loro contenuto, leggi Proprietà dei nodi: tipo, tag e contenuto. Per rispondere alle azioni degli utenti durante l'attraversamento, vedi Gestione degli eventi nel DOM.

Conclusione

Padroneggiare l'attraversamento del DOM è essenziale per creare applicazioni web dinamiche e interattive. Comprendendo e utilizzando i vari metodi e tecniche per navigare e manipolare il DOM, puoi migliorare le esperienze degli utenti e aumentare la funzionalità dei tuoi progetti web.

Pratica

Pratica
Quale dei seguenti metodi può essere usato per attraversare il DOM in JavaScript?
Quale dei seguenti metodi può essere usato per attraversare il DOM in JavaScript?
Was this page helpful?