Když prohlížeč analyzuje dokument HTML, vytvoří objektový model dokumentu (DOM). Prvky HTML jsou reprezentovány jako prvky stromu DOM, ke kterým můžete přistupovat programově v JavaScriptu.
document
je jednou z těchto metod přístupu DOM, ale není to jediná. Pojďme se podívat na ostatní metody a najít nějaké překvapení.
Přístup k NodeList
pomocí querySelectorAll
// <html>
// <head>...</head>
// <body>
// <ul>
// <li>foo</li>
// <li>bar</li>
// <li>baz</li>
// </ul>
// </body>
// </html>
const listItems = document.querySelectorAll('li');
console.log(listItems); // NodeList(3) [li, li, li]
console.log(listItems.length); // 3
for (let i = 0; i < listItems.length; i++) {
console.log(listItems[i].innerText);
}
// foo
// bar
// baz
Pokud zaznamenáte, co je vráceno document
uvidíte, že máte co do činění s NodeList
.
NodeLists
vypadají jako pole JavaScript, ale nejsou. Pokud si přečtete NodeList
Článek MDN tuto skutečnost jasně popisuje.
Překvapivě NodeLists
zadejte forEach
metoda. Tato metoda mi chyběla, když jsem začal pracovat ve vývoji webu, a byla to jedna z nástrah, na kterou jsem v průběhu let hodně narazil.
Navíc NodeList
poskytuje další metody "podobné poli", jako je item
, entries
, keys
a values
. Přečtěte si více o těchto podrobnostech v článku MDN.
querySelectorAll
je však pouze jedním způsobem přístupu k DOM. Pojďme se dozvědět více!
Kouzlo živých sbírek
Pokud si přečtete NodeList
dokumentace, možná jste si všimli "malého zábavného detailu":
Ach chlapče...
Počkat, co? Živá sbírka? V některých případech?
Ukázalo se, že NodeLists
se chovají odlišně v závislosti na tom, jak k nim přistupujete. Pojďme se podívat na stejný dokument a přistupovat k prvkům DOM odlišně.
// <html>
// <head>...</head>
// <body>
// <ul>
// <li>foo</li>
// <li>bar</li>
// <li>baz</li>
// </ul>
// </body>
// </html>
// retrieve element using querySelectorAll
const listItems_querySelectorAll = document.querySelectorAll('li');
console.log(listItems_querySelectorAll); // NodeList(3) [li, li, li]
// retrieve element using childNodes
const list = document.querySelector('ul');
const listItems_childNodes = list.childNodes;
console.log(listItems_childNodes); // NodeList(7) [text, li, text, li, text, li, text]
A NodeList
přístupné přes childNodes
obsahuje více prvků než NodeList
vráceno document
. 😲
childNodes
zahrnuje textové uzly, jako jsou mezery a zalomení řádků.
console.log(listItems_childNodes[0].textContent) // "↵ "
Ale to je jen první rozdíl. Ukázalo se, že NodeLists'
může být „živý“ nebo „statický“ .
Pojďme přidat další položku do seznamu dotazů a uvidíme, co se stane.
list.appendChild(document.createElement('li'));
// static NodeList accessed via querySelectorAll
console.log(listItems_querySelectorAll); // NodeList(3) [li, li, li]
// live NodeList accessed via childNodes
console.log(listItems_childNodes); // NodeList(8) [text, li, text, li, text, li, text, li]
😲 Jak vidíte listItems_childNodes
(NodeList
přístupné přes childNodes
) odráží prvky modelu DOM, i když byly prvky přidány nebo odebrány. Je to "naživo".
NodeList
kolekce vrácená querySelectorAll
zůstává stejný. Je to reprezentace prvků, kdy byl dotazován DOM.
To už je docela matoucí, ale vydržte. Ještě jsme neskončili...
Ne každá metoda dotazování modelu DOM vrací seznam NodeList
Možná víte, že existuje více metod dotazování DOM. getElementsByClassName
a getElementsByTagName
vám také umožní přístup k prvkům DOM.
A ukázalo se, že tyto metody vracejí něco úplně jiného.
// <html>
// <head>...</head>
// <body>
// <ul>
// <li>foo</li>
// <li>bar</li>
// <li>baz</li>
// </ul>
// </body>
// </html>
const listItems_getElementsByTagName = document.getElementsByTagName('li');
console.log(listItems_getElementsByTagName); // HTMLCollection(3) [li, li, li]
No dobře... HTMLCollection
?
HTMLCollection
obsahuje pouze odpovídající prvky (žádné textové uzly), poskytuje pouze dvě metody (item
a namedItem
) a je to živé což znamená, že bude odrážet přidané a odebrané prvky DOM.
// add a new item to the list
listItems_getElementsByTagName[0].parentNode.appendChild(document.createElement('li'));
// live HTMLCollection accessed via getElementsByTagName
console.log(listItems_getElementsByTagName); // HTMLCollection(4) [li, li, li, li]
A aby to bylo ještě složitější, HTMLCollections
jsou také vráceny, když přistupujete k DOM pomocí vlastností jako document
nebo element
.
// <html>
// <head>...</head>
// <body>
// <ul>
// <li>foo</li>
// <li>bar</li>
// <li>baz</li>
// </ul>
// </body>
// </html>
const list = document.querySelector('ul');
const listItems = list.children;
console.log(listItems); // HTMLCollection [li, li, li]
Podívejte se na specifikaci HTMLCollection
a najděte následující větu:
NodeList
a HTMLCollection
kde konkurenční standardy a nyní jsme uvízli u obou, protože nemůžeme prolomit web odstraněním funkcí.
Vývoj webu je komplikovaný
Takže shrnuto; dnes existují vlastnosti prvku DOM, jako je childNodes
(vrácení živého NodeList
) a children
(vrácení živého HTMLCollection
), metody jako querySelectorAll
(vrácení statického NodeList
) a getElementsByTagName
(vrácení živého HTMLCollection
). Přístup k DOM se nerovná přístupu k DOM!
O živých a statických sbírkách jsem ještě neslyšel, ale tento přístup k DOM mi v budoucnu ušetří spoustu času, protože najít chybu způsobenou živou sbírkou je velmi těžké.
Pokud si chcete pohrát s popsaným chováním, zkontrolujte toto CodePen.