Węzły DOM to nie ciąg znaków HTML, który wysyłasz.
DOM to nie ciąg znaków HTML, który wysyłasz. Drzewa parsowania, żywe listy węzłów oraz przepływy danych wyjaśniają, dlaczego myślenie na poziomie ciągów znaków powoduje problemy w frontendach JavaScript i React.
Źródła: The DOM Is Not Your HTML: The Difference Every Frontend Developer Should Understand
Jesteś autorem plików HTML, ale JavaScript nigdy nie edytuje tego pliku na dysku. Komunikuje się z żywym modelem obiektów dokumentu przeglądarki.
Gdy znaczniki trafiają do przeglądarki, ustala się początkowa struktura strony. Dokument może zaczynać się od prostego nagłówka:
<h1>Hello</h1>
Po załadowaniu JavaScript może znaleźć ten nagłówek i zastąpić jego tekst:
document.querySelector("h1").textContent = "Hello World";
Następnie na ekranie pojawi się:
<h1>Hello World</h1>
Może to wyglądać, jakby sam plik HTML został przepisany. Tak nie jest – plik na serwerze nadal zawiera oryginalny nagłówek. JavaScript zmienił żywy model strony w przeglądarce, czyli DOM. Oddzielenie źródłowych znaczników od tego żywego modelu ułatwia zrozumienie wielu zachowań JavaScripta.
HTML to dokument. DOM to żywa reprezentacja.
HTML to znaczniki, które tworzysz, na przykład:
<body>
<h1>Hello</h1>
<button>Click me</button>
</body>
Przeglądarka parsuje ten dokument, tworząc strukturę w pamięci. Tą strukturą jest Model Obiektów Dokumentu. Zarys procesu przekazywania danych wygląda następująco:
HTML
│
│ browser parses
▼
DOM
│
├── body
│ ├── h1
│ └── button
DOM to nie tylko odpowiednik tekstu Twojego HTML w postaci ciągu znaków. To zstrukturyzowany, programowalny dokument. To właśnie JavaScript modyfikuje.
Cóż więc faktycznie zmienia JavaScript?
Zacznij od:
<h1 id="title">Hello</h1>
Następnie uruchom:
const title = document.querySelector("#title");
title.textContent = "Welcome";
Skrypt znajduje węzeł DOM i aktualizuje jego tekst. Teraz żywy drzewo reprezentuje:
<h1 id="title">Welcome</h1>
Oryginalny źródłowy kod HTML nie został przepisany. Jeśli zapis pochodził z serwera lub pliku statycznego, ten źródło nadal zawiera:
<h1 id="title">Hello</h1>
Różnica jest niewielka, ale kluczowa: modyfikacja DOM nie jest tym samym co edycja źródłowego kodu HTML.
Oto prosty sposób, aby zobaczyć tę różnicę
Otwórz stronę i coś zmień z konsoli:
document.querySelector("h1").textContent = "Changed!";
Strona widoczna aktualizuje się natychmiastowo. Porównaj żywy DOM:
<h1>Changed!</h1>
z oryginalnym źródłem w formacie HTML:
<h1>Hello</h1>
Mogą się one różnić, ponieważ przeglądarka pracuje na podstawie swojego żywego DOM-u.
DOM to coś więcej niż to, co pierwotnie napisałeś
Załóżmy, że kod rozpoczyna się w ten sposób:
<ul id="users">
<li>John</li>
</ul>
JavaScript może dodać kolejny element:
const list = document.querySelector("#users");
const item = document.createElement("li");
item.textContent = "Sarah";
list.appendChild(item);
Drzewo żywe zawiera teraz:
ul
├── li "John"
└── li "Sarah"
„Sarah” nigdy nie pojawiła się w oryginalnym pliku. Przeglądarka nie wymaga przepisania dokumentu źródłowego; skrypt zmodyfikował żywy DOM. Ta możliwość stanowi podstawę stron interaktywnych.
DOM to drzewo
„Model obiektów” brzmi poważniej, niż w rzeczywistości jest. W najprostszej formie DOM to drzewo węzłów. Biorąc pod uwagę:
<main>
<h1>Hello</h1>
<p>Welcome!</p>
</main>
przeglądarka tworzy strukturę przypominającą mniej więcej to:
Document
│
└── main
├── h1
│ └── "Hello"
│
└── p
└── "Welcome!"
To właśnie kształt drzewa umożliwia skryptom przemieszczanie się po relacjach takich jak:
element.parentElement
element.children
element.nextElementSibling
Nie skanujesz zwykłego pliku tekstowego; poruszasz się po ustrukturyzowanym drzewie.
A DOM może się zmieniać
Oryginalny HTML jest zazwyczaj początkowym dokumentem. Drzewo w pamięci pozostaje zmienne podczas wykonywania aplikacji. Skrypty mogą alokować węzły:
const button = document.createElement("button");
ustalać atrybuty:
button.setAttribute("disabled", "");
mieniać treść:
button.textContent = "Loading...";
i wstawiać węzeł:
document.body.append(button);
Drzewo ewoluuje w miarę działania aplikacji, dlatego interaktywne interfejsy nie wymagają pełnego pobierania HTML przy każdej małej zmianie.
Jest jednak jeszcze jedna rzecz, którą programiści często mylą
Inspekcja strony może ujawnić HTML, którego nigdy nie było w oryginalnym źródle. Skrypt może stworzyć:
<div class="notification">
Saved successfully
</div>
Nie jesteś autorem tego węzła, a mimo to DevTools mogą go pokazać w panelu Elements, ponieważ ten panel odzwierciedla DOM. Funkcja View Source może całkowicie pominąć ten węzeł.
Dlatego nie ma gwarancji, że funkcje View Source i Inspect Element będą się zgadzać. Jednym z częstych powodów jest modyfikacja DOM przez JavaScript po początkowym załadowaniu HTML.
Dlaczego to ma znaczenie dla deweloperów front-endu?
Bardzo wiele zachowań w interfejsie użytkownika staje się jaśniejsze, gdy model myślowy zmienia się z „JavaScript modyfikuje HTML” na „JavaScript modyfikuje DOM”. Taki sposób postrzegania wyjaśnia takie API jak:
element.classList.add("active");
element.remove();
element.textContent = "Loading...";
element.append(newElement);
Ty te wywołania działają na żywej strukturze dokumentu. Przeglądarka następnie używa tej zaktualizowanej struktury podczas renderowania ekranu.
A co z React?
To samo rozróżnienie jest przydatne podczas nauki React. Możesz napisać:
return <h1>Hello</h1>;
JSX nie jest dosłownie DOM-em. To składnia opisująca interfejs użytkownika. React decyduje, w jaki sposób ten opis powinien zostać odzwierciedlony w rzeczywistym DOM-ie. Zrozumienie DOM-u przed zagłębieniem się w abstrakcje frameworka sprawia, że renderowanie, synchronizacja, referencje oraz aktualizacje DOM-u stają się mniej tajemnicze.
Nie musisz znać wszystkich wewnętrznych szczegółów. Pamiętaj o następującej sekwencji działań:
HTML
↓
Browser parses it
↓
DOM
↓
JavaScript can modify it
↓
Browser updates the rendered page
Frameworki dodają kolejne warstwy, ale podstawą pozostaje DOM przeglądarki.
Jeden powszechny błąd
Rozwijający czasami traktują mutację DOM-u jako trwałą modyfikację oryginalnego HTML. Tak nie jest. Na przykład:
document.body.innerHTML = "<h1>New page</h1>";
To zadanie może zastąpić obecną strukturę w czasie rzeczywistym, jednak nie zapisuje nowej wersji pliku HTML na serwerze. Odświeżenie ładuje dokument z jego źródła. Jeśli zmiana nie została zachowana i nie wygenerowano nowego HTML, powraca oryginalny dokument. To rozdzielenie oddziela stan w czasie wykonywania od treści źródłowej.
Model mentalny, który należy zapamiętać
Nie należy łączyć pomysłów w jeden:
HTML = DOM
Należy preferować tok bliższy temu:
HTML
│
│ parsed by browser
▼
DOM
│
│ modified by JavaScript
▼
Updated DOM
│
│ used during rendering
▼
What the user sees
Rozpatruj HTML jako dokument źródłowy stworzony przez autora. Rozpatruj DOM jako zmienną strukturę w przeglądarce utworzoną na podstawie tego źródła. Skrypty modyfikują tę strukturę; renderowanie odbywa się na podstawie tego, co aktualnie znajduje się w niej.
Ostateczny wniosek
Nie utożsamiaj DOM z plikiem HTML. Znaczniki to to, co sam tworzysz; model obiektów to struktura utrzymywana przez przeglądarkę, dopóki karta jest otwarta. Dzięki temu rozróżnieniu stają się oczywiste kilka kwestii: dlaczego skrypty mogą dodawać i usuwać węzły, dlaczego querySelector() działa, dlaczego textContent aktualizuje to, co widzisz, dlaczego narzędzia deweloperskie mogą pokazywać węzły, których nigdy nie stworzyłeś, dlaczego drzewo w czasie rzeczywistym może odbiegać od źródła oraz dlaczego React mimo wszystko w końcu wpływa na DOM.
Korzystna perspektywa jest prosta: piszesz HTML, przeglądarka buduje DOM, a JavaScript zmienia DOM. Gdy to zostanie zrozumiane, DOM przestaje wydawać się nieprzejrzystą API i zaczyna być tym, czym naprawdę jest – żywym przedstawieniem strony przez przeglądarkę.
Literatura pokrewna
- Pięć wskazówek dotyczących atrybutu rel, które przyspieszają pierwsze wyświetlenie treści — Użyj elementów preconnect, preload, dns-prefetch, prefetch oraz modulepreload w nagłówku dokumentu, aby przygotować połączenia i ograniczyć zużycie zasobów, bez konieczności przepisywania aplikacji.
- Kto ma kontrolować behawior interfejsu? Wybór między HTML, CSS a JavaScript — Framework do decydowania, kiedy natywne funkcje HTML i CSS, takie jak dialog, popover, :has() oraz container queries, powinny zastąpić JavaScript, a kiedy nie.