Podstawy#

Drzewo węzłów#

W ujęciu matematycznym drzewem (tree) [WikiEN|WikiPL], zgodnie z teorią grafów (graph theory) [WikiEN|WikiPL], jest graf nieskierowany (graph undirected) [WikiEN|WikiPL], który jest acykliczny i spójny, co oznacza, że z każdego wierzchołka drzewa można dotrzeć do każdego innego wierzchołka (spójność) i tylko jednym sposobem (acykliczność, brak możliwości chodzenia "w kółko").

Różne warianty drzew

Rysunek. Różne warianty drzew

W kontekście informatycznym drzewa stanowią abstrakcyjny typ danych (abstract data type) [WikiEN|WikiPL] lub strukturę danych (data structure) [WikiEN|WikiPL], która implementuje ten abstrakcyjny typ danych. W naturalny sposób przedstawiają hierarchię danych (obiektów fizycznych i abstrakcyjnych, pojęć, itp.), są więc stosowane głównie do tego celu. Drzewa ułatwiają i przyspieszają wyszukiwanie, a także pozwalają w łatwy sposób operować na posortowanych danych. Znaczenie tych struktur jest bardzo duże i ze względu na swoje własności drzewa są stosowane praktycznie w każdej dziedzinie informatyki (np. bazy danych, grafika komputerowa, przetwarzanie tekstu, telekomunikacja).

W dalszej części skupimy się raczej na aspektach czysto webowych. Dla osób początkujących szersza analiza koncepcji drzewa nie jest niezbędna do swobodnego operowania poleceniami DOM. Przejdźmy zatem do konkretów.

Budowa drzewa#

Według W3C każdy dokument bazujący na językach znacznikowych (markup-based) można wyrazić za pomocą hierarchicznej struktury zwanej drzewem węzłów # (node tree). Składnikami takiego drzewa są węzły # (nodes). Aktualna specyfikacja DOM4 definiuje 7 rodzajów węzłów, przykładowo element, ciąg tekstowy, komentarz, itd. Każdy węzeł może mieć jednego rodzica (oprócz korzenia), dowolną ilość braci i, jeśli jest elementem, to dowolną ilość dzieci.

Zrozumienie budowy drzewa węzłów oraz zależności między węzłami będzie kluczowe przy dalszej pracy z takimi technologiami webowymi jak JavaScript, DOM i CSS. Najważniejszą kwestią jest relacja między węzłami # (relationship between nodes), ponieważ pozwala ona na swobodne poruszanie się po drzewie węzłów w celu wyboru konkretnego węzła lub węzłów. Całe zagadnienie omówione zostanie na bazie ogólnej reprezentacji drzewa węzłów zgodnego z modelem DOM, które przedstawia poniższa grafika: #

Przykładowe drzewo węzłów w modelu DOM

Rysunek. Przykładowe drzewo węzłów w modelu DOM

Należący (participates)

Rodzic (parent)

Każdy obiekt należący # (participates) do drzewa węzłów posiada rodzica # (parent), którym będzie inny obiekt lub wartość null, i który zawierać może uporządkowaną listę z zerem lub większą liczbą obiektów zwanych dziećmi.

Dziecko (child)

Dzieckiem # (child) będzie każdy obiekt, który posiada rodzica. Obiekt A, którego rodzicem jest obiekt B, jest dzieckiem dla B.

Korzeń (root)

Korzeniem # (root) dla danego obiektu będzie on sam, jeśli jego rodzicem jest wartość null, w przeciwnym razie jest nim korzeń jego rodzica. Korzeniem w drzewie węzłów jest obiekt należący do tego drzewa węzłów, którego rodzicem jest wartość null.

Potomek (descendant)

Obiekt A nazywany jest potomkiem # (descendant) obiektu B, jeśli A jest dzieckiem obiektu B lub jeśli A jest dzieckiem obiektu C, który jest potomkiem obiektu B. Upraszczając sprawę mówimy, że dziecko znajduje się zawsze o jeden poziom niżej w hierarchii drzewa węzłów i może być nazywane potomkiem. Jeśli potomek położony jest o dwa lub więcej poziomów niżej w hierarchii drzewa węzłów to nie może być już nazywany dzieckiem.

Potomek obejmujący (inclusive descendant)

Potomkiem obejmującym # (inclusive descendant) dla danego obiektu będzie on sam lub jeden z jego potomków.

Przodek (ancestor)

Obiekt A nazywany jest przodkiem # (ancestor) obiektu B, wtedy i tylko wtedy, gdy B jest potomkiem dla obiektu A. Upraszczając sprawę mówimy, że przodek będzie położony o jeden lub więcej szczebli wyżej w hierarchii drzewa węzłów.

Przodek obejmujący (inclusive ancestor)

Przodkiem obejmującym # (inclusive ancestor) dla danego obiektu będzie on sam lub jeden z jego przodków.

Brat (sibling)

Obiekt A nazywany jest bratem # (sibling) obiektu B, wtedy i tylko wtedy, gdy obiekty B i A mają tego samego rodzica, którym nie jest wartość null. Upraszczając sprawę mówimy, że bracia znajdują się na tym samym poziomie zagnieżdżenia w hierarchii drzewa węzłów.

Bratem obejmującym (inclusive ancestor)

Bratem obejmującym # (inclusive sibling) dla danego obiektu będzie on sam lub jeden z jego braci.

W zastępstwie "brata" bardzo często stosuje się analogiczne określenia typu "rodzeństwo", "sąsiad" czy "rówieśnik".

Poprzednik (preceding)

Obiekt A nazywany jest poprzednikiem # (preceding) obiektu B jeśli obiekty A i B należą do tego samego drzewa węzłów i obiekt A znajduje się, zgodnie z porządkiem drzewa, przed obiektem B.

Następnik (following)

Obiekt A nazywany jest następnikiem # (following) obiektu B jeśli obiekty A i B należą do tego samego drzewa węzłów i obiekt A znajduje się, zgodnie z porządkiem drzewa, za obiektem B.

Pierwsze dziecko (first child)

Pierwszym dzieckiem # (first child) dla danego obiektu będzie jego pierwsze dziecko lub, jeśli obiekt nie ma dzieci, to wartość null.

Ostatnie dziecko (last child)

Ostatnim dzieckiem # (last child) dla danego obiektu będzie jego ostatnie dziecko lub, jeśli obiekt nie ma dzieci, to wartość null.

Brat poprzedzający (previous sibling)

Bratem poprzedzającym # (previous sibling) dla danego obiektu będzie jego pierwszy poprzednik bratowy # (preceding sibling), czyli obiekt spełniający wymagania poprzednika i brata lub, jeśli obiekt nie ma poprzednika bratowego, to wartość null.

Brat następujący (next sibling)

Bratem następującym # (next sibling) dla danego obiektu będzie jego pierwszy następnik bratowy # (following sibling), czyli obiekt spełniający wymagania następnika i brata lub, jeśli obiekt nie ma następnika bratowego, to wartość null.

Indeks (index)

Indeksem # (index) w danym obiekcie będzie liczba jego poprzedników bratowych.

Rodzaje węzłów#

Każdy węzeł w drzewie węzłów jest reprezentowany przez ogólny interfejs Node. Na przestrzeni lat różne specyfikacje DOM zdefiniowały w sumie 12 dodatkowych, bardziej wyspecjalizowanych interfejsów węzłowych dziedziczących po Node, co skutkuje podziałem węzłów na poszczególne rodzaje|typy # (type). Od typu węzła uzależnione są następujące kwestie:

Istnieje kilka sposobów pozwalających ustalić rodzaj danego węzła:

Wraz z upływem lat okazało się jednak, że tak duża liczba różnych węzłów jest zbyteczna. Mało która przeglądarka internetowa wdrożyła wszystkie ich rodzaje (chyba tylko Opera na Presto), co w rzeczywistości umożliwiło przeprowadzenie pewnych korekt w aktualnych implementacjach, bez obawy o kompatybilność wsteczną. W ramach uproszczenia platformy webowej obecna specyfikacja DOM4 pozostawiła jedynie 7 rodzajów węzłów # reprezentowanych przez następujące interfejsy (z ewentualnym uwzględnieniem interfejsów dziedziczących po nich): Element, Text, ProcessingInstruction, Comment, Document, DocumentType oraz DocumentFragment.

Wciąż nierozstrzygniętą kwestią pozostają plany zerwania z węzłowym charakterem w przypadku atrybutów oraz sekcji CDATA.

W poniższej tabeli zebrałem najważniejsze informacje charakteryzujące każdy rodzaj węzła. W dalszej części kursu wszystkie przestarzałe węzły oznaczyłem za pomocą znaku gwiazdki *, a informacje na ich temat mają charakter wyłącznie historyczny.

Typ węzłanodeTypenodeNamenodeValueOdpowiadająca stała
Element1Nazwa kwalifikowananullELEMENT_NODE
Attr*2Nazwa kwalifikowanaWartośćATTRIBUTE_NODE
Text3"#text"Dane tekstoweTEXT_NODE
CDATASection*4"#cdata-section"Dane tekstoweCDATA_SECTION_NODE
EntityReference*5NazwanullENTITY_REFERENCE_NODE
Entity*6NazwanullENTITY_NODE
ProcessingInstruction7CelDane tekstowePROCESSING_INSTRUCTION_NODE
Comment8"#comment"Dane tekstoweCOMMENT_NODE
Document9"#document"nullDOCUMENT_NODE
DocumentType10NazwanullDOCUMENT_TYPE_NODE
DocumentFragment11"#document fragment"nullDOCUMENT_FRAGMENT_NODE
Notation*12NazwanullNOTATION_NODE

Właściwości Node.nodeType, Node.nodeName oraz Node.nodeValue są dziedziczone przez poszczególne węzły z ogólnego interfejsu Node, ale zwracają wartości zależne od danego rodzaju węzła. Z węzłów można odczytać dowolną stałą, chociaż one również znajdują się w interfejsie Node. Mamy więc klasyczny schemat dziedziczenia.

Prosty przykład:

  1. L
  2. K
  3. T'
  4. T
  5. A
  6. O
  7. Z'
  8. Z
  9. #
<script>

	var doc = document; // referencja do dokumentu
	var elHTML = doc.documentElement; // referencja do elementu

	doc.write(doc.nodeType + " | " + doc.nodeName + " | " + doc.nodeValue); // 9 | #document | null
	doc.write("<br>");
	doc.write(elHTML.nodeType + " | " + elHTML.nodeName + " | " + elHTML.nodeValue); // 1 | HTML | null
	doc.write("<br>");
	doc.write(doc.DOCUMENT_NODE + " | " + doc.ELEMENT_NODE); // 9 | 1
	doc.write("<br>");
	doc.write(Node.DOCUMENT_NODE + " | " + Node.ELEMENT_NODE); // 9 | 1

</script>

Dopuszczalna zawartość węzłów#

Typ węzła determinuje jego dopuszczalną zawartość w postaci innych węzłów, które z perspektywy drzewa węzłów stanowią jego dzieci. Jest to szczególnie istotne dla parsera XML oraz niektórych metod modyfikujących drzewo węzłów, np. Node.appendChild() czy Node.insertBefore(), ponieważ są one wyczulone na wszelkie nieprawidłowości i w razie niespełnienia określonych warunków z algorytmu gwarantującego poprawność przed wstawieniem zawsze zrzucają błąd. Inaczej wygląda sytuacja w przypadku parsera HTML, który w ten czy inny sposób potrafi skorygować wszelkie napotkane błędy.

W poniższej tabeli umieściłem dopuszczalną zawartość dla wszystkich do tej pory zdefiniowanych rodzajów węzłów, co jest zgodne z aktualną specyfikacją DOM4, jak i poprzednimi wersjami specyfikacji DOM2 i DOM3.

Typ węzłaDopuszczalna zawartość (dzieci)
DocumentZgodna z porządkiem drzewa:
  1. Zero lub więcej węzłów typu ProcessingInstruction lub Comment.
  2. Opcjonalnie jeden węzeł typu DocumentType
  3. Zero lub więcej węzłów typu ProcessingInstruction lub Comment.
  4. Opcjonalnie jeden węzeł typu Element
  5. Zero lub więcej węzłów typu ProcessingInstruction lub Comment.
DocumentFragmentZero lub więcej węzłów typu Element, ProcessingInstruction, Comment, Text, CDATASection*, EntityReference*
DocumentTypeBrak
ElementZero lub więcej węzłów typu Element, ProcessingInstruction, Comment, Text, CDATASection*, EntityReference*
ProcessingInstructionBrak
CommentBrak
TextBrak
Attr*Zero lub więcej węzłów typu Text, EntityReference*
CDATASection*Brak
Entity*Zero lub więcej węzłów typu Element, ProcessingInstruction, Comment, Text, CDATASection*, EntityReference*
EntityReference*Zero lub więcej węzłów typu Element, ProcessingInstruction, Comment, Text, CDATASection*, EntityReference*
Notation*Brak

Widać wyraźnie, że niektóre węzły nie posiadają żadnej zawartości (np. Comment czy Text), dozwolony jest pojedynczy DocumentType tylko i wyłącznie w określonym miejscu dokumentu, a DocumentFragment jest o tyle specyficzny, że nie zawiera się bezpośrednio w żadnym innym węźle.

Przechodzenie po drzewie#

Przechodzenie po drzewie # (tree traversal) [WikiEN|WikiPL] to w informatyce proces odwiedzania wszystkich węzłów w drzewie węzłów. Warto napisać kilka słów na jego temat ponieważ w różnych specyfikacjach W3C często pojawiają się zwroty typu: tree order, preorder czy depth-first traversal.

Chcąc odwiedzić wszystkie węzły w drzewie węzłów można zastosować kilka znanych algorytmów wywoływanych rekurencyjnie:

Zamiast zawiłych regułek najlepiej przeanalizować wszystko na poniższym grafie drzewa binarnego # (binary tree) [WikiEN|WikiPL]:

Przykładowe drzewo binarne

Rysunek. Przykładowe drzewo binarne

W tym drzewie binarnym podstawowe algorytmy odwiedzają węzły w następującej kolejności:

Porządek drzewa (tree order)

W przypadku wielu specyfikacji webowych (włącznie z DOM i HTML) stosowany jest termin porządek drzewa # (tree order) wykorzystujący przy przechodzeniu po drzewie węzłów algorytm pre-order typu depth-first. Wiedza taka będzie pomocna przy wyjaśnianiu wielu terminów oraz poleceń operujących na węzłach.

Praktyczne przełożenie#

Przeanalizujmy jeden rzeczywisty przykład drzewa węzłów, który powinien nieco rozjaśnić całe zagadnienie. Załóżmy, że mamy do czynienia z następującą strukturą znacznikową:

  1. L
  2. K
  3. T'
  4. T
  5. A
  6. O
  7. Z'
  8. Z
  9. #
<div> <!-- Węzeł 1 (korzeń) -->
	Pierwszy blok tekstowy w DIV <!-- Węzeł 2 -->
	<p> <!-- Węzeł 3 -->
		Pierwszy blok tekstowy w P <!-- Węzeł 4 -->
		<strong> <!-- Węzeł 5 -->
			Pierwszy blok tekstowy w STRONG <!-- Węzeł 6 -->
		</strong>
		<!-- Węzeł 7 (białe znaki) -->
		<i> <!-- Węzeł 8 -->
			Pierwszy blok tekstowy w I <!-- Węzeł 9 -->
		</i>
		Drugi blok tekstowy w P <!-- Węzeł 10 -->
	</p>
	Drugi blok tekstowy w DIV <!-- Węzeł 11 -->
	<hr /> <!-- Węzeł 12 -->
	<!-- Węzeł 13 (białe znaki) -->
</div>

Jeśli zaczniemy przechodzić przez drzewo węzłów z korzeniem <div>, zgodnie z porządkiem drzewa, to otrzymamy kolejne węzły zgodnie z opisami zawartymi w komentarzach. W ramach uproszczenia komentarzy nie zaliczamy do węzłów i całkowicie pomijamy przy analizie tego przykładu, mają jedynie charakter informacyjny.

Do przechodzenia po kolejnych węzłach w drzewie węzłów najprościej zastosować iteratora węzłowego. Przejdźmy po naszym przykładowym drzewie węzłów i sprawdźmy, czy oznaczona w nim kolejność węzłów się zgadza:

  1. L
  2. K
  3. T'
  4. T
  5. A
  6. O
  7. Z'
  8. Z
  9. #
<!DOCTYPE html>
<html>

<head>

	<script>

		function iterate(direct){

			var info = document.getElementById("info");
			var root = document.getElementById("box");
			var dir = "";
			var node = "";
			var nodeText = "";

			if (typeof iterator != "object"){
				iterator = document.createNodeIterator(root, NodeFilter.SHOW_ALL, null);
			}

			if (direct == "next"){
				node = iterator.nextNode();
				dir = "nextNode()";
			}

			if (direct == "previous"){
				node = iterator.previousNode();
				dir = "previousNode()";
			}

			if (node){
				nodeText = node.textContent;
			}

			var result = "pointerBeforeReferenceNode: " + iterator.pointerBeforeReferenceNode
				+ "<br><br>" + "referenceNode: " + iterator.referenceNode
				+ "<br>" + "referenceNode.textContent: " + iterator.referenceNode.textContent
				+ "<br><br>" + dir + ": " + node
				+ "<br>" + dir + ".textContent: " + nodeText;

			info.innerHTML = result;

		}

	</script>

</head>

<body>

	<div id="box">
		Pierwszy blok tekstowy w DIV.
		<p>
			Pierwszy blok tekstowy w P.
			<strong>
				Pierwszy blok tekstowy w STRONG.
			</strong>
			<i>
				Pierwszy blok tekstowy w I.
			</i>
			Drugi blok tekstowy w P.
		</p>
		Drugi blok tekstowy w DIV.
		<hr />
	</div>

	<p>Kliknij konkretny przycisk by iterować przez kolejne węzły w kontenerze DIV.</p>
	<input type="button" value="nextNode()" onclick="iterate('next')">
	<input type="button" value="previousNode()" onclick="iterate('previous')">

	<p style="color: blue;">Szczegółowe informacje dla kolejnych przejść iteratora:</p>
	<p id="info"></p>

</body>

</html>

Przechodząc przez kolejne węzły widzimy, że maksymalnie mamy dostęp do 13 węzłów, zwracanych w tej samej kolejności, jaka opisana została w komentarzach naszej pierwotnej struktury znacznikowej.

W razie konieczności można zaimplementować swój własny algorytm przechodzenia po drzewie węzłów. Można w tym celu zastosować kod bazujący na iteracjach (pętle) lub rekurencjach (funkcje) w połączeniu z właściwościami dostępnymi dla każdego rodzaj węzła.

Implementacja w przeglądarkach#

Zanim przejdę dalej wypadałoby wyjaśnić kilka podstawowych kwestii z pogranicza ECMAScript i DOM, które się zazębiają, a które mogą być trudne do zrozumienia przez początkujących.

Każde środowisko uruchomieniowe ECMAScript (określane jako gospodarz lub host) musi zapewnić obiekt globalny pod zmienną this, dostępną poza jakąkolwiek funkcją. W obiekcie tym znajdują się wszystkie właściwości oraz metody globalne (pochodzące z samego rdzenia języka ECMAScript oraz od gospodarza) jak i nasze zmienne i metody globalne pochodzące ze wszystkich jednostek kompilacji (czyli znaczników <script>). Niestety, JavaScript nie ma narzędzia linkującego, dlatego wszystko to trafia do jednego obiektu globalnego, co jest niedobrą cechą języka, no ale tak już jest.

W przeglądarkach internetowych obiekt globalny posiada właściwość window, która tak naprawdę wskazuje na niego samego, czyli this.window = this. W różnych środowiskach właściwość ta może mieć inną nazwę, a nawet może w ogóle nie występować, dlatego należy ostrożnie podchodzić do wskazywania obiektu globalnego przy użyciu konkretnej nazwy.

Dla rozjaśnienia tematu zadeklarujmy funkcję wypisz() w przestrzeni globalnej. Można się później do niej odwołać na kilka sposobów:

  1. L
  2. K
  3. T'
  4. T
  5. A
  6. O
  7. Z'
  8. Z
  9. #
<script>

	function wypisz(){
		document.write("Cześć" + "<br>");
	}

	wypisz(); // Cześć
	this.wypisz(); // Cześć
	this.window.wypisz(); // Cześć
	window.wypisz(); // Cześć

	document.write(window); // [object Window]
	document.write("<br>");
	document.write(this == window); // true
	document.write("<br>");
	document.write(this.window == window); // true

</script>

Z początku może wydawać się to skomplikowane, ale w praktyce pozwala pominąć poprzedzające odwołania do wszystkiego, co umieszczone w globalnej przestrzeni, gdyż są one zbędne i niepotrzebnie rozwlekają kod. Mogą też mieć wpływ na wydajność (w starszych przeglądarkach), ponieważ każde dookreślenie (kropka) odpowiada za przeszukiwanie zagnieżdżonych obiektów. Reasumując, wystarczy sama nazwa globalnej właściwości, funkcji, zmiennej itd.

Obiekt window możemy potraktować jak całe okno przeglądarki. Dopiero w nim znajduje się obiekt document, który reprezentuje każdy dokument XML oraz HTML. W obiekcie document zawarte będą informacje na temat każdego węzła, a jeśli tworzony będzie w przeglądarce może też posiadać inne cechy (np. adres, ciasteczka, tytuł itp.). W obiekcie document znajdować się będzie korzeń dokumentu. W przypadku dokumentów (X)HTML korzeniem będzie obiekt html. Warto zauważyć, że można swobodnie przejść z html do document (gdyż obydwa obiekty są częścią tego samego drzewa węzłów). Przejście z document do window nie jest już możliwe.

Kilka przypadków testowych:

  1. L
  2. K
  3. T'
  4. T
  5. A
  6. O
  7. Z'
  8. Z
  9. #
<script>

	document.write(window + "<br>"); // [object Window]
	document.write(document + "<br>"); // [object HTMLDocument]

	document.write(document.documentElement + "<br>"); // [object HTMLHtmlElement]
	document.write(document.documentElement.parentNode + "<br>"); // [object HTMLDocument]

	document.write(document.parentNode + "<br>"); // null

</script>

Dla jeszcze lepszego zobrazowania całości najlepiej przeanalizować następującą strukturę znacznikową:

  1. L
  2. K
  3. T'
  4. T
  5. A
  6. O
  7. Z'
  8. Z
  9. #
<!DOCTYPE html>
<html>

	<head>
		<title>Przykład</title>
	</head>

	<body>
		<p>Treść akapitu.</p>
		<div><strong>Wyróżniona treść</strong> wewnątrz kontenera.</div>
	</body>

</html>

Struktura DOM (wygenerowana za pomocą świetnego narzędzia Live DOM Viewer) dla naszego przykładu będzie postaci:

#Trzeba zwracać uwagę na fakt, że w drzewie DOM pojawiają się węzły tekstowe, które mogą być wstawiane bez naszej zamierzonej/świadomej ingerencji. Oznaczyłem je poprzez gwiazdkę (*). Pisałem już o tym we wstępie do HTML5. Przy iteracji po kolekcjach (zawierających wszystkie rodzaje węzłów) zazwyczaj będziemy chcieli/musieli pomijać takie domyślne wstawki.

Każdy węzeł w drzewie DOM jest reprezentowany w JavaScript przez obiekt. Wszystkie obiekty mają zdefiniowane API (zgodne z tymi opisanymi przez specyfikację DOM), dzięki którym można je modyfikować. Trzeba tutaj odróżniać samo drzewo węzłów (jako coś teoretycznego, obrazującego relację między węzłami drzewa) oraz przełożenie/implementację tego drzewa w danym środowisku (np. struktura obiektowa w JavaScript).

O samym JavaScripcie jako języku programowania można napisać dwie kluczowe definicje: #

Pierwsze określenie jest dyskusyjne. Co prawda JavaScript ma obiekty, możemy je tworzyć/zmieniać itd., ale brakuje mu wielu rzeczy wywodzących się z prawdziwych języków obiektowych (np. klas, metod prywatnych, publicznych, uprzywilejowanych itp.). Ze względu na dużą elastyczność i ekspresyjność języka wiele z tych braków można uzyskać w inny sposób, stosując jedynie pierwotne elementy składowe języka. Kolejne wersje ECMAScript na bieżąco starają się uzupełniać różne braki - spore zmiany szykują się w szóstym wydaniu standardu. Prawdę powiedziawszy prosta składnia języka oferująca możliwość jego samodzielnego rozszerzania jest lepsza niż stosy gotowych poleceń.

Druga sprawa jest bezdyskusyjna, co można zaobserwować po opisach i przykładach umieszczonych w kursie. W zasadzie wszystko, co zauważysz w kodzie języka JS, ma sporą szansę być obiektem. Jedynie pięć typów podstawowych nie jest obiektami: liczba, ciąg znaków, wartość logiczna, null i undefined. Dodatkowo pierwsze trzy mają swoje obiektowe reprezentacje i mogą zostać łatwo zamienione w obiekt przez programistę - metody Number(), String(), Boolean() - a czasem są nawet zamieniane automatycznie przez interpreter języka. Obiekty są umieszczane w innych obiektach tworząc zwięzłą strukturę danych.

Wszystkie obiekty w JavaScripcie można podzielić na dwie główne grupy: #

Obiekty gospodarza będą specyficzne. Co prawda umożliwiają one rozszerzanie ich w pewnym zakresie, ale nie jest to w żaden sposób standaryzowane przez jakąkolwiek specyfikację i może być w różny sposób udostępniane przez każdy program. W praktyce możliwości ich zmiany będą obwarowane wieloma ograniczeniami i "dziwnymi" zachowaniami, różniącymi się nieco od standaryzowanych rozwiązań ECMAScript.

Praca z wieloma dokumentami i drzewami węzłów jednocześnie#

Programując w środowisku jakim jest przeglądarka internetowa będziemy mieli do czynienia z wieloma rodzajami drzew węzłów. Jest to pojęcie zbyt ogólne, dlatego warto wprowadzić kilka konkretyzujących terminów.

Podział na wiele rodzajów drzew węzłów został wprowadzony przeze mnie osobiście w celu uproszczenia opisów różnych poleceń DOM w dalszej części kursu. Chociaż może nie być w 100% prawidłowy, z praktycznego punktu widzenia pozwala połączyć ze sobą wiele pojęć, takich jak:

Drzewo węzłów

Drzewem węzłów # będzie każde drzewo, którego elementami są węzły opisywane w specyfikacji DOM. Może być to drzewo dokumentu, aktualne drzewo dokumentu, drzewo węzłów poza dokumentem itd. Nawet pojedynczy węzeł może stanowić drzewo węzłów, w takim wypadku jest jego jedynym elementem. Każdy z poniższych rodzajów będzie przynależał do drzewa węzłów.

Drzewo węzłów z tym samym właścicielem

Drzewo węzłów z tym samym właścicielem # to drzewo z węzłami, których właścicielem jest ten sam obiekt typu Document, co w przypadku innego drzewa węzłów. Mogą to być drzewa węzłów poza drzewem dokumentu, poddrzewa węzłów z drzewa dokumentu lub aktywnego drzewa dokumentu.

Drzewo węzłów poza drzewem dokument

Drzewo węzłów poza drzewem dokument # to drzewo z węzłami, które nie należy bezpośrednio do drzewa dokumentu (obiektu typu Document). Mogą to być drzewa z węzłami utworzonymi przez nas samych za pomocą odpowiednich poleceń, np. Document.createElement(). Węzły mogą być przenoszone/kopiowane między dokumentami, mogą być też z nich usuwane, w takich przypadkach zawsze najpierw znajdują się poza drzewem dokumentu, do którego zostały dodane lub z którego zostały usunięte. Do tej grupy zalicza się także kolejne drzewa dokumentów utworzone przez nas samych lub wczytane za pomocą Ajaxa.

Drzewo dokumentu

Drzewo dokumentu # (inaczej zwane drzewem DOM), to drzewo z węzłami, które należą bezpośrednio do obiektu typu Document. Będą to aktywne drzewa dokumentów, drzewa dokumentów utworzone przez nas samych lub dokumenty załadowane i sparsowane przy użyciu Ajaxa. Między różnymi drzewami dokumentów można przenosić dowolne węzły, służą do tego celu metody Document.adoptNode() lub Document.importNode().

Aktywne drzewo dokumentu

Aktywne drzewo dokumentu #, to drzewo dokumentu, które umieszczone jest w bieżącym oknie lub ramkach.

Przeglądarka internetowa wczytując stronę spod wskazanego adresu URL tworzy jej obiektową reprezentację w pamięci operacyjnej maszyny. Strona ta będzie reprezentowana przez obiekt typu Document, który domyślnie występuje pod globalną właściwością document. W aktywnym dokumencie mogą znajdować się znaczniki, które pozwalają na jednoczesne wczytywanie kolejnych dokumentów, przykładem będzie ramka lokalna <iframe>. Z głównej strony WWW (obiekt document) możemy odwołać się do podrzędnego aktywnego dokumentu w ramce lokalnej, wystarczy wywołać następujące polecenie document.getElementById("ramka").contentDocument.

Oczywiście z poziomu skryptu można tworzyć kolejne dokumenty HTML czy XML, które na początku zawsze będą nieaktywnymi drzewami dokumentów i jednocześnie drzewami węzłów poza drzewem dokumentu. Każdy z tych nowych dokumentów może zostać umieszczony w zmiennej/właściwości o dowolnej nazwie.

Drzewo renderowania

Drzewo renderowania # jest drzewem istotnym z punktu widzenia CSS. Znajduje się w nim wszystko to, co zostanie wyświetlone z aktywnego drzewa dokumentu. Niektóre węzły mogą należeć do aktywnego drzewa dokumentu, ale nie będą wyświetlane, przykładowo komentarze lub znacznik <head> z całą swoją zawartością.

Zasadniczo podział na drzewo węzłów i aktywne drzewo dokumentu jest najistotniejszy z punktu widzenia programisty. Drzewo węzłów (w tym drzewo dokumentu), które ma być wyświetlone, musi zostać dołączone do odpowiedniej części aktywnego drzewa dokumentu, czyli takiej, która będzie miała pudełkowe odpowiedniki w drzewie CSS, np. do obiektu body. Ewentualnie fragmenty drzewa węzłów mogą być dodawane także do odpowiedniej części aktywnego drzewa dokumentu w obiektach reprezentujących zawartość ramek.

Można przyjąć, że każdy obiekt reprezentujący aktywny dokument ma tylko jedno drzewo renderowania. Nawet jeśli będzie on posiadał kolejne dokumenty w ramkach, tak naprawdę będziemy mieli do czynienia z pojedynczymi drzewami renderowania w każdym z nich.

Liczba drzew węzłów poza aktywnym drzewem dokumentu jest w zasadzie nieograniczona. Można tworzyć lub wczytywać dowolną ilość nowych dokumentów, pojedynczych węzłów, lub niewielkich drzew węzłów. Należy pamiętać, że każdy węzeł ma jakiegoś właściciela w postaci obiektu typu Document, nawet jeśli nie jest on aktualnie wyświetlany lub znajduje się poza drzewem dokumentu. W niektórych przypadkach węzły takie mogą być automatycznie usuwane w procesie oczyszczania pamięci.

Przykładowe polecenia CSS i DOM#

W poniższej tabeli zamieszczam kilka selektorów oraz metod i właściwości DOM przydatnych przy poruszaniu się po drzewie dokumentu. Punktem odniesienia będzie węzeł Dany element widoczny na pierwszej grafice w tym rozdziale.

Węzeł (PL)Węzeł (EN)Selektor CSSMetoda/Właściwość DOM
KorzeńRoot:root, htmlelement.ownerDocument.documentElement
RodzicParentbrakelement.parentNode
PrzodekAncestorbrakelement.parentNode.parentNode
Poprzedni sąsiadPrevious siblingbrakelement.previousSibling
Następny sąsiadNext siblingelement + sąsiadelement.nextSibling
Pierwsze dzieckoFirst childelement > dziecko:first-childelement.firstChild
Ostatnie dzieckoLast childelement > dziecko:last-childelement.lastChild
Wszystkie dzieciChildrenelement > *element.childNodes
PotomkowieDescendantselement *element.getElementsByTagName('*')

Selektory CSS nie dają możliwości wybrania przodków, ani poprzedników, aby ułatwić przeglądarkom wyświetlanie progresywne (element występujący później w dokumencie nie powinien mieć wpływu na wcześniejsze) oraz uniemożliwić zrobienie kombinacji selektorów dających błędne koło. Nie wykluczone, że przyszłe specyfikacje CSS uzupełnią braki nowymi mechanizmami odpornymi na problematyczne zachowania.

Pasek społecznościowy

SPIS TREŚCI AKTUALNEJ STRONY

Podstawy (H1) Drzewo węzłów (H2) Budowa drzewa (H3) Należący (participates) (H4) Rodzic (parent) (H4) Dziecko (child) (H4) Korzeń (root) (H4) Potomek (descendant) (H4) Potomek obejmujący (inclusive descendant) (H4) Przodek (ancestor) (H4) Przodek obejmujący (inclusive ancestor) (H4) Brat (sibling) (H4) Bratem obejmującym (inclusive ancestor) (H4) Poprzednik (preceding) (H4) Następnik (following) (H4) Pierwsze dziecko (first child) (H4) Ostatnie dziecko (last child) (H4) Brat poprzedzający (previous sibling) (H4) Brat następujący (next sibling) (H4) Indeks (index) (H4) Rodzaje węzłów (H3) Dopuszczalna zawartość węzłów (H3) Przechodzenie po drzewie (H3) Porządek drzewa (tree order) (H4) Praktyczne przełożenie (H4) Implementacja w przeglądarkach (H2) Praca z wieloma dokumentami i drzewami węzłów jednocześnie (H3) Drzewo węzłów (H4) Drzewo węzłów z tym samym właścicielem (H4) Drzewo węzłów poza drzewem dokument (H4) Drzewo dokumentu (H4) Aktywne drzewo dokumentu (H4) Drzewo renderowania (H4) Przykładowe polecenia CSS i DOM (H2)