2013. november 26.

jQuery kód optimalizálása

10 perc olvasási idő

jQuery kód optimalizálása

Fejlesztés során mindig igyekszünk spórolni az erőforrásokkal, ám kliens oldalon hajlamosak vagyunk erről megfeledkezni, gondolván “pár sornyi JavaScript, észre sem veszi a felhasználó”. Ám a responsive oldalak elterjedésével egyre fontosabbá vált minden egyes http request, minden fogadott bájt és minden egyes lefuttatott JavaScript parancs, hiszen az oldal betöltődési sebességén túl annak működési gyorsasága is fontos.

Az erősebb hardverrel megáldott okostelefonoknak meg sem kottyan egy slider megjelenítése, viszont gondolni kell azokra is, akiknek régebbi, gyengébb eszköz adatott meg. Összegyűjtöttem pár tipikus példát tesztekkel, melyeket figyelembe véve jelentősen gyorsítható a kód futásának sebessége, ezáltal javítva a felhasználói élményt.

Natív JavaScript vs jQuery

A jQuery függvénytár jelentősen megkönnyíti már évek óta a front-end fejlesztők életét. Szinte mindenre létezik jQuery plugin és ezt sokszor lusta fejlesztőként ki is használjuk. A jQuery többek között azért lett létrehozva, hogy az eltérő JavaScript motorok közötti különbségeket áthidalja. Mára azonban a helyzet egyre inkább normalizálódik, így több esetben lehet használni a natív megoldásokat. Persze, ez nem azt jelenti, hogy el kéne felejtenünk mindenféle keretrendszert és az azokkal járó egyszerűbb megoldásokat, csupán érdemes néha a működés mögé nézni és a natív megoldást választani a hatékonyság érdekében.

Ha csak pár sornyi JavaScriptet szeretnél futtatni az oldaladon, a legegyszerűbb függvényekkel (pl. show(), hide()), akkor teljesen felesleges behúzni egy komplett függvénytárat, ami legjobb esetben is további 40-50Kb adatforgalommal és egy http request-tel jár.

getElementByID()

A legegyszerűbb példának egy adott id-vel rendelkező elem elérése tekinthető, aminél jelentős különbség van a natív és jQuery megoldások között. Az eredmény magáért beszél.

getElementByID használatával nagyságrendekkel több művelet végezhető el másodpercenként

Szóval, ha a sebességet szem előtt tartva, és csak egy elemre akarsz hivatkozni, érdemes a


var foo = $('#foo');

helyett a


var foo = document.getElementById('foo');

formulát használni.

Selectorok optimalizálása

Ha összetettebb módon szeretnél egy, vagy több elemet elérni, akkor érdemes a működés mögé nézni. Egy szelektor megadása esetén a jQuery a natív megoldásokhoz fordul (getElementById(), getElementsByTagName(), getElementsByClassName()), míg komplexebb lekérés esetén már külön fel kell dolgoznia az egyes tagokat és azok keresését. Ha megszokod ezeket az alapvető szabályokat, egy idő után már tudat alatt optimális kódot adsz ki a kezedből.

Használd a find() függvényt a komplex selectorok helyett.


$('#id p'); // lassabb
$('#id').find('p'); // gyorsabb

Törekedj arra, hogy elkerüld a selectorok halmozását, amikkel számolnia kell a  jQuery-nek, hiszen az csak lassítja a futást. Az alábbi példában teljesen felesleges a table.foo selector, amennyiben csak egy táblázatod van, amiben bar class-szal rendelkező cella van.


$('.data table.foo td.bar'); //lassabb
$('.data td.bar'); //gyorsabb

Ha nem tudsz konkrét selectorral hivatkozni egy elemre, próbáld minél pontosabban elérni a felesleges elemek mellőzésével.


$('.foo > *'); // a lehető leglassabb
$('.foo').children(); // egy fokkal jobb
$('.foo :radio'); // inkább kerüljük a használatát
$('.foo *:radio'); // az előzővel megegyezik
$('.foo input:radio'); // a :radio jQuery-specifikus
$('.foo input[type=radio]'); // az optimális megoldás

Használj for-t az $.each() helyett

A jQuery által nyújtott $.each() függvény jóval lassabb, mint a natív for kifejezést használó megoldás. Igaz, az each esetén össze tudsz kötni műveleteket (chaining), de az esetek többségében úgysem élsz ezzel a lehetőséggel.

for használatával nagyságrendekkel több művelet végezhető el másodpercenként

Az alábbi egyszerű példát tesztelve is az jön ki, hogy a natív megoldás háromszor gyorsabb, mintha jQuery-vel járnád be a tömböt. Ugyanez az eredmény objektumok bejárása esetén is igaz.


var foo = new Array();
foo[0] = 'egy';
foo[1] = 'ketto';
foo[2] = 'harom';

$.each(foo, function(index, value) {
    value.test= 1;
});

var lng = foo.length;
for(var i = 0; i < lng; i ++) {
    foo[i].test = 1;
}

Kerüld a túlzott DOM manipulálást

Természetesen nem tudod teljesen kikerülni, hiszen legtöbbször ezért használsz JavaScriptet, de érdemes átgondolni, miként is nyúlsz a DOM-hoz. Ez a művelet minden esetben lassú, nincs mit tenni, de például egy naptár beszúrását több féle módon is megteheted.


var $calendar = $('<table></table>');
for (var i = 0; i < 6; i++) {
 var $tr = $('<tr></tr>');
 for (var j = 0; j < 7; j++) {
   $tr.append('<td></td>');
 }
 $calendar.append($tr);
}

A fenti megoldás a szép és elfogadott, ám a lentebbi mindenképpen gyorsabb és bizonyos esetekben célratörőbb.


var $calendar = $('<table><tr><td><td>…</tr><tr>…</tr>…</table>');

Window.load a document.ready helyett

Nem olyan meghatározó a sebességet illetően, de érdemes pár szót szólni a két eseménnyel kapcsolatban. A document.ready az oldal renderelésekor fut le, amikor még töltödhtenek be/le objektumok. Ez kisebb akadásokat okozhat az oldal megjelenítésében. Barátibb megoldás a processzor kihasználtságának szempontjából, ha minden elem letöltése után kezdjük el futtatni a scriptjeinket, azaz a window.load eseményhez kötjük.

Szelektorok ismétlésének elkerülése - objektumok cachelése

Sokszor előfordul, hogy ugyanazt a selectort több alkalommal kell használnod egymás után. Sebesség szempontjából nem mindegy, hogy a script hányszor járja be a DOM-ot, hogy megtalálja azt a selectort. Roppant egyszerű, ám igen hatékony módja az optimalizálásnak, ha ilyen esetben “lecacheled” a selectort.

cache használatával nagyságrendekkel több művelet hajtható végre másodpercenként, mint cache nélkül

Például:


$('div.foo div.bar div.elem1').someFunc();
$('div.foo div.bar div.elem2').someOtherFunc();
$('div.foo div.bar div.elem3').someTotallyOtherFunc();

Optimalizálva:


var $bar = $('div.foo div.bar');
$bar.find('div.elem1').someFunc();
$bar.find('div.elem2').someOtherFunc();
$bar.find('div.elem3').someTotallyOtherFunc();

Ugyanez az eset áll fenn, amikor egy elem CSS attribútumait szeretnéd módosítani. Ilyenkor tömbként átadhatod a beállítandó értékeket, továbbá cachelés helyett összefűzheted az egyes funkciókat, ezzel is kihagyva a folyamatot, hogy a jQuery minden esetben bejárja a DOM-ot a selectort keresve.


$('p').css('color', 'blue');
$('p').css('font-size', '1.2em');
$('p').text('Lorem Ipsum');

A gyorsabb és erőforrás-takarékosabb forma:


$('p').css({'color': 'blue', 'font-size': '1.2em'}).text('Lorem Ipsum');

Másik, szintén hatékonyabb forma:


var p = document.getElementByTagName('p');
$(p).css('color', 'blue');
$(p).text('Lorem Ipsum');

Hogyan mérj?

Ha meg akarsz bizonyosodni számszerűen is arról, mennyire sikeresen írtad át a kódot, több megoldás is lehetséges. A jsPerf oldalon egyszerűen tesztelhetsz kódrészleteket, de akár komplett scripteket is. Amellett, hogy saját teszteket készíthetsz, mások által készített benchmarkok között is böngészhetsz, érdemes kattintgatni, mert érdekes eredményekkel szembesülhet az ember.

Természetesen kézenfekvő dolog a kódba beillesztett időbélyegek összehasonlítása, viszont kevés adat nem feltétlenül ad reprezentatív eredményt.


var start = new Date().getTime();

for (i = 0; i < 50000; ++i) {
   //foo
}

var end = new Date().getTime();
var time = end - start;
alert('Futásidő: ' + time);

A Chrome DevTools és a FireBug hatékonyan használható a JavaScript kód debugolására, teljesítmény mérésére és elég részletes jelentést ad ahhoz, hogy feltérképezhesd a kód lassú pontjait és javíthasd az esetleges sebességet csökkentő hibákat.

Tóth Zoltán

Vezető fejlesztő. Közel tíz éve foglalkozik webfejlesztéssel, igyekszik egyaránt backend és frontend területen is képben lenni. WordPresspárti, böngészőkiegészítő- és bookmarkletgyűjtő.

Tóth Zoltán

Hozzászólások