JavaScript Restart – QuerySelector

Setkávám se často s vývojáři jejichž první úvaha nad implementací začíná napsáním “$”. jQuery je všudypřítomné, a pro tyto vývojáře je překvapení, že se dá psát bez něj.
Seriál: JavaScript Restart (4 díly)
- JavaScript Restart – QuerySelector 11. 2. 2015
- JavaScript Restart – Hurá na pole 23. 2. 2015
- JavaScript Restart – Neidentifikovatelný létající objekt 9. 3. 2015
- JavaScript Restart – Restartováno 27. 3. 2015
Nálepky:
Na obzoru je vydání nové verze Internet Exploreru nebo dokonce nového prohlížeče od Microsoftu a tudíž je prakticky neudržitelná podpora IE 8 i pro velké korporátní společnosti. Když jsem začal zjišťovat, co si budeme moci při psaní webových aplikací dovolit, uvědomil jsem si, že je možná na čase se rozloučit se všudypřítomným jQuery, jelikož do JavaScriptu (a souvisejících API) s podporou ES5 přibylo nemálo užitečných funkcí.
Pojďme tedy v tomto seriálu udělat restart a dát čistému Javascriptu ještě šanci.
Jak najít element v DOM postaru
Před dávnými a dávnými věky, milí vývojáři, byla modlou všech JavaScriptových vývojářů tato funkce:
document.getElementById('id');
a ta sloužila (a slouží) k nalezení elementu podle jeho id. (Pokud není nalezen žádný element, getElementById vrací hodnotu null.)
document.getElementById() má ještě několik příbuzných funkcí:
- document.getElementsByTagName()
- document.getElementsByName()
- document.getElementsByClassName()
které vrací pole elementů (popř. prázdné pole, není-li nalezeno nic), leč ty se vyskytovaly v kódu řádově méně častěji než výše zmíněná funkce document.getElementById().
Nelze zde nevzpomenout na legendární funkci document.all, zavedenou Internet Explorerem (V současné době není doporučeno ji používat a v IE11 už dokonce nefunguje). Tato funkce byla námětem mnoha učených disputací a flamewarů.
document.all['id'];
Tato funkce byla natolik oblíbená, že ji s velkým odporem implementoval i Firefox, což způsobilo kurozní situaci. Totiž test pomocí:
if (document.all) {}
se zvesela používal pro detekci Internet Exploreru, což, jak už dnes víme, je velmi, velmi ošklivé. Jenže jelikož Firefox nechtěl být detekován jako Explorer a zároveň chtěl umět document.all, musel tento test vracet vždy hodnotu false.
Pokud přesto po něčem takovém jako document.all toužíte, podobný výsledek dostanete při zavolání:
document.getElementsByTagName('*')
Jak najít element pomocí CSS selektorů v jQuery
Když se řekne jQuery, každý si představí dotazování na DOM pomocí CSS selektorů (přesto, že jQuery toho umí mnohem víc). DOM query pomocí CSS je báječný nápad, protože každý vývojář webových aplikací CSS musí znát a pracuje s ním prakticky denně.
Např:
var element = $('body div');
kde výstupem je pole elementů vyhovující zadanému dotazu. Podobně lze výraz napsat takto
var element = $('div', 'body');
kde druhým parametrem říkáme, že se dotaz se má omezit pouze na body – to bývá velmi užitečné, pokud chceme, aby naše komponenta nemohla ovlivnit ostatní. Velmi doporučuji používat.
V případě, že element není nalezen, dotaz vrací prázdné pole [].
CSS selectory v DOM
Pole všech elementů vyhovujících selectoru dostaneme takto
document.querySelectorAll('body div');
což je vlastně ekvivalentem dotazu v jQuery.
Dotaz nevrací pole, jak by se dalo po vzoru jQuery čekat, ale NodeList, což způsobí, že jej lze sice procházet jako pole (má totiž metodu item), bohužel další metody jako např.forEach nemá.
Pokud ovšem touha po polích neustává, lze to vyřešit takto:
var nodes = document.querySelectorAll('div'),
divArray = [].slice.call(nodes);
V ES 6 je to jednodušší, díky metodě Array.from, která je zatím implementována jenom ve Firefoxu:
var divArray = Array.from(div);
Pokud má být výsledkem pouze jeden element, pak použijeme jinou funkci:
document.querySelector('body div');
a výsledek je první nalezený element. Pokud dojde k situaci, že element není nalezen, document.querySelector vrací hodnotunull.
Zároveň je možno zadat skupinu selektorů (stejně jako v jQuery):
document.querySelector('#element1, #element2');
document.querySelectorAll('#element1, #element2');
pak výsledkem bude pro document.querySelector první nalezený element, pro document.querySelectorAll pak NodeList s elementy.
Zde je třeba upozornit na jeden rozdíl oproti jQuery, srovnejte:
$(''); => []
document.querySelector(''); => DOMException
document.querySelectorAll(''); => DOMException
jQuery vrací stále prázdné pole (ostatně pořád pracuje s polem elementů), zatímco querySelector v případně prázdného dotazu, vyhodí výjimku DOMException. Na to je třeba myslet a nezapomínat.
QuerySelector a querySelectorAll jsou zároveň metody, které dostal “do vínku” každý DOM element, tudíž, pokud potřebujeme vyhledávat v potomcích elementu, pak
var element = $('div', 'body');
můžeme nahradit
var element = document.querySelector('body').querySelectAll('div');
Nativní funkce jsou logicky výkonnější než funkce jQuery, což je jistě dobrý důvod k jejich použití.
Pojďme se podívat, co se stane, když CSS selektor nebude syntakticky správně:
var uglyQuery = 'div [';
$(uglyQuery);
document.querySelectorAll(uglyQuery);
pokud předpokládáte, že oba selectory skončí výjimkou, pak předpokládáte správně, jen je třeba si uvědomit, že jQuery vyhodí obecnou výjimku Error, zatímco document.querySelector pochopitelně vyhodí výjimku DOMException.
Zápis document.querySelectorAll je prostě dlouhý a nebudu se vůbec divit, bude-li se zkracovat. Je to funkce jako každá jiná, řekneme si, takže ji jen stačí přiřadit
var q = document.querySelectorAll;
ale ouha, dojde k výjimce, a to k výjimce Illegal invocation, která se nevidí každý den.
Pro moderní prohlížeče a IE9+ je řešení docela elegantní:
var q = document.querySelector.bind(document);
pro prohlížeče nepodporující metodu bind pak nezbývá než udělat toto:
var q = function(q) { return document.querySelector(q); };
ClassList
Milým objevem (pro mne) je, že pro operace s třídami jako jako je addClass, removeClass, toggleClass nepotřebuji jQuery a taktéž na starý způsob s nastavováním řetězce classNameuž můžu zapomenout.
Nejdříve postaru:
// addClass
element.className += ' active';
// removeClass
element.className = element.className.replace('active', '').trim();
Funkci toggle si doplní laskavý čtenář sám, já už to, doufám, nikdy nebudu muset psát.
V jQuery to dokážeme o poznání elegantněji
$(element).addClass('active');
$(element).removeClass('active');
$(element).toggleClass('active');
$(element).hasClass('active');
Od IE 10 lze použít toto:
element.classList.add('active');
element.classList.remove('active');
element.classList.toggle('active');
element.classList.contains('active');
Na první pohled by se mohlo zdát, že classList obsahuje pole, ale Array takové metody jako toggle nemá. Je to tím, že classList vrací DOMTokenList, datový typ pro hodnoty v DOM, oddělované mezerou.
Perlička na závěr
Při ověřovaní informací výše uvedených, jsem narazil na funkci elementu s podivným názvem insertAdjacentHTML. Domníval jsem se že jde o novinku, a tak mě překvapilo, že je implementována v IE4.
Druhým překvapením bylo, že by měla být o poznání výkonnější než nastavení vlastnosti innerHTML.
Použít se dá místo:
$(el).after(html);
a to takto:
el.insertAdjacentHTML('afterend', html);
Inu člověk se učí pořád.
V článku je jedna nepřesnost: funkce jQuery (nebo častěji $) nevrací pole, nýbrž objekt jQuery.
Dojem, že $ vrací pole, může podpořil Chrome Developer Tools, který (nepochopitelně) v konzoli objekty jQuery vypisuje úplně stejně, jako by šlo o pole. Proto třeba na školeních jQuery jsem vždycky trval raději na používání Firebugu, který to vypisuje správně.
Objekt jQuery ukládá podobně jako pole prvky pod číselné klíče a má proměnnou length, tudíž se s ním dá trošku jako s polem pracovat (říká se tomu array-like object), ale další metody, jako například v článku zmíněnou forEach, nemá ani jQuery. Pro převod jQuery na pole slouží metoda
toArray()
.Ale jinak fajn článek. jQuery splnilo svou důležitou historickou úlohu a většinu jeho někdejších úloh dnes zvládá nativní DOM. jQuery je dnes knihovna pro AJAXové requesty a promise pattern ;-)
Ajéje, to jsem si nikdy neuvědomil a Chrome Console mne nachytala. Inu člověk se učí pořád.
Pod větu
bych se i podepsal.
O XHR a promisích třeba někdy příště.
Dev tools to loguje ako array preto, lebo ma v prototype ulozenu array.prototype.splice.
https://gist.github.com/danielhusar/6030dcf3e615e7f482f1
Pár dalších nepřesností:
element.className.replace('active', '').trim();
odstraní nejen tříduactive
, ale iactive
ze všeho, co to obsahuje jako podřetězec. Obvykle se používalo něco jako.replace(/(^| )active( |$)/g, ' ')
.document.all
není funkce, ale vlastnost.item
, ale tím, že mají vlastnostlength
.Ani pro ten promise pattern není, protože jQuery ho zkurvila a nikdy neopraví. tl;dr Nepracuje vůbec s chybama.
Funkce document.getElementsBy* nevrací pole, ale
HTMLCollection
. Tato kolekce má sice propertu length a k prvkům je možné přistupovat přes index (podpbně jako v případě objektu jQuery), ale oproti nim jeHTMLCollection
„živá“. Kolekce je automaticky aktualizována pokud dojde ke změně v dokumentu.Funkce
document.querySelectorAll
vrací statickýNodeList
. V tomto případě nedochází k automatické aktualizaci. Zde je nutné upozornit také na existenci „živého“ NodeList. V tomto typu jsou uloženy napříkladchildNodes
.Chapem motivaciu zanechat jQuery a vratit sa k nativnym funkciam Javascriptu, ale prave jedna z vlastnosti, pre ktore je jQuery tak oblubene je moznost venovat sa inym veciam, nez mysliet na to, ci ta a ta implementacia bude podporovana v tom a tom prehliadaci.
A ked som si pocas citania clanku uvedomil, ze asi polovica textu je venovana tomu ako treba to a to volanie upravit aby bolo podporovane v tom a tom prehliadaci, tak som si povedal, ze toto nebude pre mna.
Všechno v článku funguje od IE 10, tady na cca 90-95 % prohlížečů. Tedy pro tyto nejčastější úkoly už pomalu přestává být jQuery potřeba. Na některých webech už dnes, na jiných brzy.
No právě, IE10 je už hodně velký luxus. Běžně se setkávám spíše s požadavky na IE9, občas i IE8. Často i na WinXP. Situace ve velkých korporátech je smutná. A může jít klidně i o veřejný web, ale když si ho ředitěl se svou IE8 neprohlídne, tak je úplně jedno, že 95% zbytku světa ano :)
I když není vše tak špatné, třeba jeden zákazník teď od nového roku IE8 upustil a začal používat rovnou IE11.
Souhlas. Děláme veřejný web, ředitel IE 8 nemá, ale zato business má statistiku prohlížečů uživatelů. Teprve asi před rokem jsem prosadil, že kulaté rohy se už konečně mohou dělat přes border-radius a ne obrázky, ale pořád platí, že uživatel s IE 8 musí mít plnohodnotný přístup k aplikaci, byť s povolenými grafickými odlišnostmi, jako třeba ty nekulaté rohy.
Na podzim jsme dělali jedno zobrazení, na které by bylo super inline SVG. Bohužel, to by nefungovalo v IE 8, takže to neprošlo a musím to kreslit Javascriptem v Raphael… :(
Tak holt vypadá komerční realita mimo technologická dema a startupy :) Když mám na webu pár návštěvníků, tak se s IE 9 / IE 8 patlat nevyplatí, ale pro navštěvovanější weby dávají v absolutních číslech pořád dost lidí, aby se vyplatilo kvůli tomu vývojáře prudit.
Ve finale stejne clovek potrebuje nejaky obal na tu nativni API, protoze ta API je skvela v tom, ze dava nekonecne moznosti pouziti, ale chtel bych si implementovat chainovani, atd… Kazdopadne v IE10+ je to otazka cca jednoho dne napsat si knihovnicku na selector a manipulaci s nody. Stejne tak ajaxy nejsou zadna veda s XHR a FormData.
Jen bych jeste dodal k clanku, ze jak insertAdjacentHTML tak innerHTML je akorat tak cesta pro XSS a navic budu mit nekde v stringu seredny HTML. Takze pokud to delam u klienta, tak si to muzu krasne sestavit z document.createElement, document.createTextNode a pokud si posilam HTML, ze serveru tak bych si mel byt sakra jisty co tam mam, nez to takhle vlozim do stranky.
Docela šikovný web pro rychlé oživení „čistého“ javascriptu po letech používání jQuery – http://youmightnotneedjquery.com/
Ten clanok prave krasne ukazuje ze JQuery ma zmysel. Kazdy zapis v JQuery je kratsi ako v nativnom HTML a tym padom aj prehladnejsi… Od toho su kniznice, aby sme zbytocne nepisali kod, ktory uz napisal niekto iny, ale venovali sa podstate problemu, ktory riesime.
Faktická: není to náhodou tak, že knihovna Sizzle (se kterou pracuje jQuery) používá querySelectorAll pokud je dostupné? řádek na Gitu jako důkaz…
Tím se ale nesnažim tvrdit, že by práce s nativním JS nebyla rychlejší. I kdyby byly ve výsledku použity stejné metody, knihovny budou mít vždy režii navíc. Na druhou stranu knihovny se těší podobnějšímu chování napříč prohlížečovým spektrem, člověk se pak nemusí starat o vyjímky a fallbacky.
Pokud jde ale v prvé řadě o výkon, není těžké si jednoduchý fallback napsat:
if(!document.querySelectorAll) document.querySelectorAll = Sizzle;
Vzhledem k tomu, že jQuery 2 většinu z těchto novinek používá, tak nevidím důvod jej nepoužít. Minimálně kvůli větší přehlednosti a úspornosti kódu.