Podstawy#
API selektorów#
Jednym z najbardziej uniwersalnych sposobów wybierania elementów są selektory CSS. Okazały się na tyle intuicyjne i użyteczne, że technikę tę postanowiono przenieść także do DOM. Początkowo odbywało się to za sprawą autorów bibliotek JavaScript/DOM, gdzie szczególną rolę odegrało tutaj jQuery. Biblioteka jQuery swoją największą popularność zdobyła dzięki wygodnemu mechanizmowi selekcji elementów, którego idea i działanie było zbliżone do selektorów CSS. Śladem jQuery poszły pozostałe popularne biblioteki, dlatego też selektory CSS znalazły praktyczne zastosowanie nie tylko w obszarze CSS, ale i w środowisku skryptowym.
Z biegiem czasu W3C utworzyło osobną specyfikację "Selectors API Level 1", w której zdefiniowano dwie metody querySelector()
i querySelectorAll()
, przyjmujące jako argument łańcuch znakowy w postaci selektorów CSS. Dokument uzyskał status rekomendacji 21 lutego 2013 roku. Prace nad "Selectors API Level 2" wstrzymano, a umieszczone tam nowości przeniesiono do aktualnego DOM4 oraz kolejnego modułu CSS: "Selectors Level 4".
Trzeba zdawać sobie sprawę z tego, że najnowsze rozwiązania wciąż znajdują się w fazie rozwoju i mogą ulegać licznym zmianom. Praktyczne implementacje to perspektywa wielu przyszłych miesięcy.
Metody zaliczane do API selektorów mają swoje specyficzne zachowania. W tym miejscu, w razie potrzeby, opisane zostaną kluczowe kwestie istotne z punktu widzenia programisty.
Zwięzłość zapisu#
Metody API selektorów upraszczają wiele operacji jakie należałoby wykonać w przypadku stosowania analogicznych metod DOM zdefiniowanych w przeszłości. Załóżmy że mamy następującą strukturę znacznikową:
<table id="punkty">
<thead>
<tr>
<th>Test
<th>Wynik
</tr>
</thead>
<tfoot>
<tr>
<th>Średnia
<td>82%
</tr>
</tfoot>
<tbody>
<tr>
<td>A
<td>87%
</tr>
<tr>
<td>B
<td>78%
</tr>
<tr>
<td>C
<td>81%
</tr>
<tbody>
</table>
W celu pobrania komórek zawierających wyniki w tabeli, które np. mogą być wykorzystane do utworzenia wykresu, można zastosować co najmniej dwa rozwiązania. Starsze podejście zakłada utworzenie skryptu, w którym iterujemy po wszystkich wierszach tr
wewnątrz wszystkich tbody
tabeli, a następnie wybranie drugiej komórki td
w każdym wierszu tr
:
<script>
var table = document.getElementById("punkty");
var groups = table.tBodies;
var rows = null;
var cells = [];
for (var i = 0; i < groups.length; i++){
rows = groups[i].rows;
for (var j = 0; j < rows.length; j++){
cells.push(rows[j].cells[1]);
}
}
</script>
Alternatywne użycie metody querySelectorAll()
upraszcza zapis całego skryptu do jednego polecenia:
<script>
var cells = document.querySelectorAll("#punkty>tbody>tr>td:nth-of-type(2)");
</script>
Statyczność i wydajność#
Metody wchodzące w skład API selektorów, takie jak queryAll()
czy querySelectorAll()
, są zaliczane do kolekcji statycznych. Dzięki temu ryzyko utworzenia nieskończonych pętli jest o wiele mniejsze, niż w przypadku kolekcji aktualnych. W większości przypadków kolekcje statyczne są tym, czego oczekują programiści, dlatego też polecenia selekcji elementów wchodzące w skład zewnętrznych bibliotek DOM (np. jQuery) zwracają wyłącznie odpowiedniki kolekcji statycznych.
Jeśli chodzi o wydajność to sytuacja jest niejednoznaczna. W przypadku prostego wyboru, np. elementu z określonym identyfikatorem lub nazwą tagu, standardowe metody DOM okazują się szybsze. Sytuacja wygląda inaczej w chwili, kiedy należy wydobyć elementy w oparciu o bardziej złożoną filtrację. Dodatkowe iteracje i poziomy filtracji konieczne dla standardowych metod DOM, które w zasadzie muszą zostać zinterpretowane po stronie ECMAScript, będą kosztowniejsze niż użycie natywnych poleceń API selektorów. Widać to wyraźnie na przykładzie starszych wersji bibliotek DOM, gdzie brak obsługi natywnych poleceń generował znaczne koszta.
Trzeba pamiętać też o tym, że w przypadku implementacji API selektorów - zgodnie z najnowszym modułem selektorów - mamy do czynienia z kompletnym profilem, w którym spadki wydajności, zależnie od rodzaju używanych selektorów, są dopuszczalne. Tak czy inaczej wybieranie elementów w oparciu o selektory stało się na tyle powszechne, że osoby odpowiedzialne za poszczególne implementacje poświęcają wiele uwagi, aby zapewnić ich jak największą optymalizację.
Zawężenie selektorów#
Metody związane z API selektorów stosują inną metodę zawężenia (tzw. filtrujące zawężenie selektorów), niż dzieje się to w przypadku chociażby elementu <style>
. Dla osób początkujących może być to źródłem pewnej dezorientacji. Zamiast skomplikowanych definicji najlepiej przeanalizować jakiś przykład praktyczny:
<!DOCTYPE html>
<html>
<head>
<script>
window.onload = function(){
var info = document.getElementById("info");
var scope = document.querySelector("#scope");
var result = "document.querySelectorAll('ul a').length: " + document.querySelectorAll("ul a").length; // 2
result += "<br><br>" + "scope.querySelectorAll('a').length: " + scope.querySelectorAll("a").length // 1
+ "<br>" + "scope.querySelectorAll('ul a').length: " + scope.querySelectorAll("ul a").length // 1
+ "<br>" + "scope.querySelectorAll('body ul a').length: " + scope.querySelectorAll("body ul a").length; // 1
result += "<br><br>" + "scope.querySelectorAll(':scope a').length: " + scope.querySelectorAll(":scope a").length // 1
+ "<br>" + "scope.querySelectorAll(':scope ul a').length: " + scope.querySelectorAll(":scope ul a").length // 0
+ "<br>" + "scope.querySelectorAll(':scope body ul a').length: " + scope.querySelectorAll(":scope body ul a").length; // 0
info.innerHTML = result;
}
</script>
</head>
<body>
<p>Lista nieuporządkowana z trzeba elementami, gdzie pierwszy i trzeci zawierają odsyłacz.</p>
<ul>
<li id="scope"><a href="">Jabłko</a> (id="scope")</li>
<li>Śliwka</li>
<li><a href="">Gruszka</a></li>
</ul>
<p style="color: blue;">Liczba elementów w poszczególnych kolekcjach:</p>
<p id="info"></p>
</body>
</html>
Istotne będą przypadki wywołania metody querySelectorAll()
na konkretnym elemencie, czyli w naszym przykładzie na elemencie li
z identyfikatorem "scope"
, który wskazywany jest przez zmienną scope
. Selektory w postaci "a"
, "ul a"
i "body ul a"
dopasowywane są w kontekście wszystkich elementów dokumentu, a nie jak mogłoby się wydawać, jedynie wewnątrz scope
, z tym że temat selektora musi znajdować się w scope
. Efektem tego jest zwrócenie cyfry 1
dla wszystkich trzech pierwszych przypadków.
Powyższe zachowanie może być nieco mylące, gdyż w rzeczywistości zależy nam na wybraniu elementów wewnątrz konkretnego elementu, zaczynając dopasowywanie selektora właśnie od tego konkretnego elementu. Zresztą tak zachowują się biblioteki DOM, chociażby przytaczane wcześniej jQuery:
$(scope).find("ul a").length // 0
$(scope).find("body ul a").length // 0
Problem można rozwiązać na dwa sposoby. Pierwszy polega na rozpoczęciu selektora od pseudoklasy :scope
, przez co selektor dopasowany zostanie jedynie w kontekście elementu scope
i wszystkich jego potomków (zobacz trzy ostatnie warianty przykładu). Drugi sposób to wykorzystanie najnowszych metod query()
lub queryAll()
, które jako argument przyjmują selektor relatywny, ale na ich wsparcie ze strony przeglądarek przyjdzie nam jeszcze poczekać.
Można zatem stwierdzić, że wszystkie metody zaliczane do API selektorów dzielą się na dwie grupy, gdzie argumentem może być:
- selektor absolutny #:
querySelector()
,querySelectorAll()
imatches()
- selektor relatywny #:
query()
iqueryAll()
W przypadku selektora relatywnego jest on automatycznie absolutyzowany, dlatego samodzielne dodawanie pseudoklasy :scope
nie jest konieczne. Na chwilę obecną jedynie Firefox i Chrome obsługują pseudoklasę :scope
w metodach API selektorów.
Pozostałe uwagi#
Warto wspomnieć jeszcze o kilku luźnych uwagach, które są istotne przy pracy z metodami API selektorów.
Pomimo tego, że identyfikator elementu powinien być unikatowy w obrębie drzewa węzłów, to i tak metody API selektorów przeanalizują wszystkie takie elementy. Identyczne zachowanie występuje chociażby w przypadku selektorów CSS umieszczanych w kodzie HTML. Tak czy inaczej lepiej unikać tego typu sytuacji bo jest to niezgodne ze składnią XML.
Argument w metodach API selektorów powinien spełniać ogólną składnię CSS oraz składnię selektorów CSS. W zasadzie mamy tutaj do czynienia z połączeniem światów ECMAScript i CSS, dlatego w przypadku stosowania zastrzeżonych znaków CSS należy umieścić podwójny znak uniku, jeden dla CSS i jeden dla ECMAScript.
Stosowanie pseudoelementów jest dozwolone w argumencie metod API selektorów, ale taka konstrukcja nigdy niczego nie dopasuje. Tworzenie tego typu poleceń nie ma większego sensu i jest odradzane.
Podobnie jest w przypadku przekazania wartości null
lub undefined
. Polecenia z takimi wartościami muszą zostać obsłużone zgodnie z wytycznymi Web IDL; nie powodują błędu, choć nigdy niczego nie dopasują, zatem pożytek z nich niewielki.
Poziom obsługi metod API selektorów zależy głównie od implementacji poszczególnych selektorów CSS. Warto podkreślić, że obsługa selektorów CSS wewnątrz elementu <style>
nie musi wcale oznaczać obsługi w metodach API selektorów (i odwrotnie). Najlepiej stosować tylko te selektory, które upowszechniły się we wszystkich programach.