Część trzecia. Zagadnienia zaawansowane
Wydajność — najlepsze rozwiązania
W tym rozdziale omówionych zostanie kilka najlepszych rozwiązań dotyczących biblioteki jQuery i języka JavaScript. Kolejność poruszanych zagadnień jest bez znaczenia. Znaczna część rozdziału została napisana w oparciu o prezentację Paula Irisha jQuery Anti-Patterns for Performance.
Jeśli korzystasz z pętli, zapisuj długość w pamięci podręcznej
W przypadku pętli for nie należy pobierać długości tablicy przy każdej iteracji — lepiej wcześniej zapisać ją w pamięci podręcznej.
var mojaDlugosc = mojaTablica.length;
for (var i = 0; i < mojaDlugosc; i++) {
// zrób coś
}
Dodawaj nową treść poza pętlą
Naruszanie struktury DOM obciąża pamięć — jeśli więc chcesz dodać wiele elementów, zrób to za jednym podejściem.
// tak nie powinno się robić
$.each(mojaTablica, function(i, pozycja) {
var nowaPozycjaNaLiscie = '<li>' + pozycja + '</li>';
$('#koszykarze').append(nowaPozycjaNaLiscie);
});
// lepiej zrobić to tak
var frag = document. createDocumentFragment();
$.each(mojaTablica, function(i, pozycja) {
var nowaPozycjaNaLiscie = '<li>' + pozycja + '</li>';
frag. appendChild(nowaPozycjaNaLiscie);
});
$('#koszykarze')[0].appendChild(frag);
// bądź zrób to tak
var mojHtml = '';
$.each(mojaTablica, function(i, pozycja) {
html += '<li>' + pozycja + '</li>';
});
$('#koszykarze').html(mojHtml);
Nie powtarzaj się
Stosuj się do powyższej zasady — jeśli się powtarzasz, to robisz coś źle.
// ZŁY kod
if ($zdarzeniefade.data('obecnie') != 'pokazuje') {
$zdarzeniefade.stop();
}
if ($zdarzeniehover.data('obecnie') != 'pokazuje') {
$zdarzeniehover.stop();
}
if ($spany.data('obecnie') != 'pokazuje') {
$spany.stop();
}
// DOBRY kod!
var $elementy = [$zdarzeniefade, $zdarzeniehover, $spany];
$.each($elementy, function(i,elem) {
if (elem.data('obecnie') != 'pokazuje') {
elem.stop();
}
});
Strzeż się funkcji anonimowych
Wszędobylskie funkcje anonimowe są bardzo uciążliwe. Trudno jest wyszukać w nich usterki, a także je przetestować czy ponownie wykorzystać. Kłopoty sprawia również ich odpowiednie utrzymanie. Zamiast nich do organizacji i nazywania procedur obsługi oraz wywołań zwrotnych skorzystaj z literału obiektowego.
// ZŁY kod
$(document).ready(function() {
$('#magia').click(function(z) {
$('#superefekty').slideUp(function() {
// ...
});
});
$('#szczescie').load(url + ' #jednorozce', function() {
// ...
});
});
// LEPIEJ
var PI = {
gotowy : function() {
$('#magia').click(PI.cukierkowaGora);
$('#szczescie').load(PI.url + ' #jednorozce', PI.wzJednorozca);
},
cukierkowaGora : function(z) {
$('#superefekty').slideUp(PI.wzPrzesuniecia);
},
przesunWz : function() { ... },
jednorozecWz : function() { ... }
};
$(document).ready(PI.gotowy);
Zoptymalizuj selektory
Optymalizacja selektorów nie ma obecnie aż tak dużego znaczenia, ponieważ coraz więcej przeglądarek ma zaimplementowaną funkcję document.querySelectorAll()
— oznacza to, że wybór elementów spoczywa na samej przeglądarce, a nie na bibliotece jQuery. Warto jednak zapamiętać kilka wskazówek.
Selektory oparte na identyfikatorach
Wybieranie elementów zawsze najlepiej zacząć od ich identyfikatora.
// szybki sposób
$('#kontener div.ramierobota');
// super szybki sposób
$('#kontener').find('div.ramierobota');
Korzystanie z metody $.fn.find
jest szybsze, ponieważ pierwszy wybór dokonywany jest bez wykorzystania silnika Sizzle. Wybieranie elementów wg selektorów opartych na identyfikatorze obsługiwane jest przez metodę document.getElementById()
, która jest rodzimą metodą przeglądarki — stąd też sposób ten jest bardzo szybki.
Precyzyjność
Zadbaj o to, by prawa strona selektora była bardziej sprecyzowana, zaś lewa mniej.
// niezoptymalizowany selektor
$('div.data .gonzalez');
// zoptymalizowany
$('.data td.gonzalez');
Jeśli to możliwe, po prawej stronie utwórz selektor wg wzoru znacznik.klasa
, a po lewej uwzględnij tylko klasę.
Nie przesadzaj z dokładnością.
$('.data table.attendees td.gonzalez');
// lepsze rozwiązanie: jeśli to możliwe, zrezygnuj ze środkowej części selektora
$('.data td.gonzalez');
Bardziej „płaska” struktura DOM również poprawia szybkość znajdowania elementów, ponieważ liczba warstw, które muszą zostać przejrzane w poszukiwaniu wybranego elementu jest mniejsza.
Unikaj selektora uniwersalnego
Selektory określające lub wskazujące na to, że wybrane elementy można znaleźć w dowolnym miejscu są bardzo wolne.
$('.przyciski > *'); // bardzo duży koszt pamięciowy
$('.przyciski').children(); // o wiele lepiej
$('.gatunek :radio'); // w istocie jest to selektor uniwersalny, choć może się wydawać inaczej
$('.gatunek *:radio'); // to samo, tylko teraz jest to oczywiste
$('.gatunek input:radio'); // o wiele lepiej
Korzystaj z delegacji zdarzeń
Dzięki delegacji zdarzeń można powiązać procedurę obsługi z jednym kontenerem (np. z nieuporządkowaną listą) zamiast z wieloma elementami znajdującymi się w tym kontenerze (np. z pozycjami na liście). Zadanie ułatwiają dostępne w bibliotece jQuery metody $.fn.live
i $.fn.delegate
. Kiedy to tylko możliwe należy korzystać z metody $.fn.delegate
, ponieważ dzięki niej unikniemy wybrania zbędnych elementów, a jej konkretny kontekst zmniejsza narzut o około 80% (por. z kontekstem dokumentu metody $.fn.live
).
Delegacja zdarzeń nie tylko pozwala zwiększyć wydajność. Dzięki niej możemy również dodawać nowe elementy kontenera na stronę bez potrzeby ponownego wiązania ich z procedurami obsługi — te są już dodane.
// źle (jeśli mamy do czynienia z długą listą)
$('li.wyzwalacz').click(PrObslugi);
// lepiej skorzystać z delegacji zdarzeń za pomocą metody $.fn.live
$('li.wyzwalacz').live('click', PrObslugi);
// najlepiej zaś skorzystać z delegacji zdarzeń za pomocą metody $.fn.delegate
// można łatwo określić kontekst
$('#mojaLista').delegate('li.wyzwalacz', 'click', PrObslugi);
Odłącz elementy, by rozpocząć na nich pracę
Model DOM jest wolny, dlatego najlepiej ograniczyć wykonywane na nim manipulacje do minimum. W wersji jQuery 1.4 wprowadzono metodę $.fn.detach
, która pomaga uniknąć tego problemu — dzięki niej można usunąć ze struktury DOM element podczas pracy nad nim.
var $tabela = $('#mojaTabela');
var $rodzic = $tabela.parent();
$tabela.detach();
// ...tutaj dodaj wiele rzędów tabeli
$rodzic.append(tabela);
Do zmieniania stylów CSS wielu elementów skorzystaj z arkuszy stylów
Jeśli zmieniasz styl ponad 20 elementów, zamiast używać metody $.fn.css
warto dodać do strony element style, co przyspieszy jej działanie o prawie 60 %.
// dobry sposób dla maksymalnie 20 elementów, w przypadku większej ilości kod działa wolno
$('a.swedberg').css('color', '#asd123');
$('<style type="text/css">a.swedberg { color : #asd123 }</style>')
.appendTo('head');
Korzystaj z metody $.data zamiast $.fn.data
Wywołanie metody $.data
na elemencie DOM zamiast metody $.fn.data
na elementach wybranych przez jQuery może być do dziesięciu razy szybsze. Przed skorzystaniem z tego sposobu upewnij się jednak, że znasz różnicę pomiędzy elementem DOM a zestawem elementów jQuery.
// tradycyjny sposób
$(elem).data(klucz,wartość);
// 10 razy szybciej
$.data(elem,klucz,wartość);
Nie wykonuj operacji na niewybranych elementach
Jeśli spróbujesz wykonać obszerny blok kodu na pustym zbiorze elementów, jQuery nic nie zasygnalizuje — kod zostanie wykonany normalnie. Sam musisz sprawdzić, czy wybrany przez ciebie zestaw zawiera jakieś elementy.
// ZŁY SPOSÓB: kod wykona trzy funkcje
// zanim zorientuje się, że zbiór
// jest pusty
$('#brakczegostakiego').slideUp();
// Lepiej
var $wybraneElementy = $('#brakczegostakiego');
if ($wybraneElementy.length) { $wybraneElementy.slideUp(); }
// NAJLEPSZE rozwiązanie: dodaj wtyczkę wykonajRaz
jQuery.fn.wykonajRaz = function(funk){
this.length && funk.apply(this);
return this;
}
$('li.dodanedokoszyka').wykonajRaz(function(){
// dodaj Ajax! o/
});
Rada ta dotyczy zwłaszcza widżetów jQuery UI, w przypadku których występują znaczne narzuty, jeśli nie uda nam się wybrać żadnych elementów.
Definiowanie zmiennych
Wszystkie zmienne można zdefiniować za pomocą zaledwie jednej instrukcji.
// stary i sfatygowany sposób
var test = 1;
var test2 = function() { ... };
var test3 = test2(test);
// nowe cacko
var test = 1,
test2 = function() { ... },
test3 = test2(test);
W przypadku samowykonujących się funkcji, definicja zmiennej może zostać całkowicie pominięta.
(function(foo, bar) { ... })(1, 2);
Instrukcje warunkowe
// stary sposób
if (typ == 'foo' || typ == 'bar') { … }
// lepiej
if (/^(foo|bar)$/.test(typ)) { … }
// przeszukiwanie literału obiektowego
if (({ foo : 1, bar : 1 })[typ]) { ... }
Nie traktuj jQuery jak czarnej skrzynki
Korzystaj z dokumentacji w postaci kodu źródłowego — dodaj stronę https://github.com/jquery/jquery do zakładek i często ją odwiedzaj.
Bardzo dobry artykuł ! Od dziś będę zwracał dużo więcej uwagi na wydajność moich skryptów.