Ověřit, že element, který jste našli, je ten, který jste hledali, se dá v jQuery jednoduše ověřit pomocí funkce is()
:
if (el.parent().is('.menu')) { el.addClass('menu-item'); }
Problém je ale v tom, že funkce is()
je dost pomalá a její použití výrazně zpomaluje prováděný kód.
Funkce is()
totiž musí nejprve rozparsovat a pochopit zadaný řetězec (např. v předchozím příkladu, že vás zajímá, zda má prvek CSS třídu menu), následně najít všechny příslušné prvky a porovnat, jestli je mezi nimi ten, na který se ptáte – což jsou dvě nejnáročnější operace v jQuery vůbec.
Proč to tedy jQuery neusnadnit a nezeptat se rovnou na to, co vás zajímá?
Třeba zrovna v případě CSS třídy se nabízí mnohem lepší a rychlejší metoda hasClass()
:
if (el.parent().hasClass('menu')) { el.addClass('menu-item'); }
V tomto případě jQuery rovnou ví, že vás zajímá CSS třída a navíc ví, na kterém prvku, takže pouze projde ClassList
(nebo ClassName
u starších prohlížečů) a podívá se, jestli tam hledaná třída je nebo není.
A jak ukazují výkonové testy, přímá kontrola třídy může být až 10x rychlejší než ověření pomocí is()
.
Stejně tak pokud vás zajímá konkrétní atribut, jako id
, type
, href
apod., můžete se zeptat přímo metody attr()
nebo prop()
místo složitého vyhledávání přes is()
:
el.is('#menu'); //pomalé el.attr('id') === 'menu'; //rychlé el.is('input'); //pomalé el.prop('tagName') === 'INPUT'; //rychlé el.is('[type=password]'); //pomalé el.attr('type') === 'password'; //rychlé
Všechno nebo nic
Další případ pomalého hledání nastává v okamžiku, kdy k prvku hledáte konkrétního rodiče (nebo jiného příbuzného). Např. chcete ověřit, že je prvek uvnitř menu, ale nevíte, na jaké úrovni (takže nelze použít výše uvedený příklad s přímým rodičem).
//hodně pomalé isMenuItem = $el.parents('.menu').length > 0; //pomalé isMenuItem = $el.closest('.menu').length > 0; //rychlé isMenuItem = (($el.prop('tagName') === 'LI' || $el.prop('tagName') === 'A') && $el.closest('ul').hasClass('.menu'));
První řádek kontroluje, jestli alespoň jeden rodič prvku má třídu menu
. Což ovšem znamená, že musí projít všechny rodiče až to rootu, a to je pomalé. Druhý příkaz je urychlen tím, že používá metodu closest()
, která zastaví po nalezení prvního odpovídajícího elementu, takže v případě, že je prvek skutečně uvnitř menu, nemusí jít až do rootu. Ale pokud to prvek menu není, stále musí projít vše až do rootu, aby se o tom přesvědčila.
Třetí příkaz je nejrychlejší, protože využívá konkrétní situace, kdy menu je tvořeno pomocí UL-LI-A
struktury a tak může nejprve ověřit, že prvek je LI
nebo odkaz (z nichž se menu skládá) a nebude tedy procházet DOM pro DIV
y a SPAN
y, které v menu vůbec nejsou. Následně, protože ví, že LI
může být jen uvnitř prvku UL
, najde nejbližší UL
a jednoduše ověří, jestli má třídu menu
; pokud ji nemá, program ví, že to není součást menu a nemusí už dál hledat.
To IS or not to IS
Samozřejmě i funkce is()
má své opodstatnění, a to hlavně ve složitějších případech, kdy nelze jednoduše určit konkrétní hodnota, podle které se dá prvek určit (nebo by takové určení bylo příliš zdlouhavé a nepřehledné:
//pomalejší, ale přehledné isInput = el.is('input,select,textarea'); //rychlejší, ale ne tak přehledné isInput = (el.prop('tagName') === 'INPUT' || el.prop('tagName') === 'SELECT' || el.prop('tagName') === 'TEXTAREA');
A co teprve tenhle případ:
//pomalejší, ale velmi přehledné isInput = el.is('input[type=text],' + 'input[type=password],textarea'); //rychlejší, ale úplně nepřehledné isInput = ((el.prop('tagName') === 'INPUT' && (el.attr('type') === 'text' || el.attr('type') === 'password')) || el.prop('tagName') === 'TEXTAREA');
V posledním případě budete mít problém jen správně zapsat závorky a velikost písmen a při každé změně hrozí, že něco z toho popletete.
A samozřejmě nesmíme zapomínat na tenhle případ:
isVisible = el.is(':visible');
Tady by se ověření viditelnosti prvku dělalo jen těžko, vzhledem k tomu, co všechno ji může ovlivňovat (viditelnost rodičů, jejich rozměry, overflow
, atd.)