Poprawianie wydajności aplikacji HTML5

Wstęp

HTML5 daje nam świetne narzędzia, które poprawiają wygląd aplikacji internetowych. Zwłaszcza w świecie animacji. Jednak wraz z tą nową mocą wiążą się też nowe wyzwania. W rzeczywistości te wyzwania nie są zbyt nowe i czasem warto zapytać znajomą sąsiadkę z biurka, programistkę Flasha, o to, jak poradziła sobie z podobnymi problemami w przeszłości.

Podczas pracy w animacji niezwykle ważne jest, aby użytkownicy postrzegali je jako płynne. Musimy zdawać sobie sprawę, że płynności animacji nie da się uzyskać przez zwiększenie liczby klatek na sekundę powyżej progu poznawczego. Niestety nasz mózg jest od tego mądrzejszy. Rzeczywiste 30 klatek animacji na sekundę (FPS) to znacznie więcej niż 60 klatek na sekundę z zaledwie kilka klatek umieszczonych w środku. Ludzie nienawidzą nierówności.

W tym artykule spróbujemy udostępnić Ci narzędzia i techniki, które pomogą Ci poprawić wrażenia użytkowników korzystających z Twojej aplikacji.

Strategia

W żaden sposób nie chcemy zniechęcać Cię do tworzenia wspaniałych aplikacji wizualnych w HTML5.

Gdy stwierdzisz, że można poprawić wydajność, wróć tutaj i przeczytaj, jak można poprawić elementy aplikacji. Oczywiście od samego początku może pomóc w wykonywaniu pewnych czynności od samego początku, ale nigdy nie przeszkodzi Ci to w produktywności.

Wizualizacja++ w HTML5

Akceleracja sprzętowa

Akceleracja sprzętowa jest ważnym czynnikiem wpływającym na ogólną wydajność renderowania w przeglądarce. Ogólny schemat zakłada przeniesienie zadań, które w innym przypadku zostałyby obliczone przez procesor główny, na procesor graficzny (GPU) w karcie graficznej komputera. Może to spowodować znaczny wzrost wydajności, a także zmniejszyć zużycie zasobów na urządzeniach mobilnych.

Te aspekty dokumentu mogą być przyspieszane przez GPU

  • Ogólne komponowanie układu
  • Przejścia CSS3
  • Przekształcenia 3D CSS3
  • Rysunek na płótnie
  • Rysunek 3D WebGL

Mimo że akceleracja kanw i WebGL to specjalne funkcje, które mogą nie mieć zastosowania w przypadku konkretnej aplikacji, jednak pierwsze 3 aspekty mogą przyczynić się do szybszego działania każdej aplikacji.

Co można przyspieszyć?

Przyspieszenie GPU polega na odciążeniu dobrze zdefiniowanych i określonych zadań w sprzęcie specjalnego. Ogólnie dokument jest podzielony na wiele „warstw”, które są niezmienne w przypadku przyspieszonego aspektu strony. Te warstwy są renderowane za pomocą tradycyjnego potoku renderowania. Procesor GPU jest następnie używany do skomponowania warstw na jednej stronie i stosowania „efektów”, które można przyspieszać w czasie rzeczywistym. W efekcie animowany obiekt na ekranie nie wymaga pojedynczego „przekaźnika” strony podczas animacji.

Musisz tylko ułatwić silnikowi renderowania określenie, kiedy może zastosować jego magię akceleracji GPU. Na przykład:

Mimo że to działa, przeglądarka nie wie, że wykonujesz coś, co powinno zostać odebrane jako płynna animacja przez człowieka. Zastanów się, co się stanie, gdy uzyskasz ten sam wygląd za pomocą przejść CSS3:

Sposób implementacji animacji przez przeglądarkę jest całkowicie niewidoczny dla dewelopera. To z kolei oznacza, że przeglądarka może stosować sztuczki, takie jak przyspieszanie GPU, by osiągnąć określony cel.

Istnieją 2 przydatne flagi wiersza poleceń Chrome, które ułatwiają debugowanie przyspieszenia GPU:

  1. --show-composited-layer-borders pokazuje czerwone obramowanie wokół elementów przetwarzanych na poziomie GPU. Warto sprawdzić, czy manipulacje mają miejsce w warstwie GPU.
  2. --show-paint-rects. Wszystkie zmiany, które nie dotyczą GPU, są malowane, przez co wszystkie pomalowane obszary są jasne. Widzimy, jak przeglądarka optymalizuje malowane obszary w działaniu.

Safari ma podobne flagi środowiska wykonawczego opisane tutaj.

Przejścia CSS3

Przejścia CSS sprawiają, że animacje stylów są trywialne, ale są też bardzo inteligentne. Jako że przejściem CSS zarządza przeglądarka, wierność animacji można znacznie poprawić, a w wielu przypadkach można to zrobić z akceleracją sprzętową. Obecnie WebKit (Chrome, Safari, iOS) oferuje transformacje CSS z akceleracją sprzętową, ale szybko ta funkcja działa w innych przeglądarkach i na innych platformach.

Możesz używać zdarzeń transitionEnd, aby tworzyć na ich podstawie skuteczne kombinacje, ale na razie zarejestrowanie wszystkich obsługiwanych zdarzeń końcowych przejścia wymaga oglądania webkitTransitionEnd transitionend oTransitionEnd.

W wielu bibliotekach wprowadziliśmy interfejsy API animacji, które korzystają z przejść, jeśli występują, lub powracają do standardowej animacji w stylu DOM. W przeciwnym razie: scripty2, przejście YUI, jQuery anmate Enhanced.

Tłumacz CSS3

Na pewno zdarzyło Ci się już kiedyś animować pozycję x/y elementu na stronie. Prawdopodobnie manipulowałeś właściwościami lewo i góra stylu wbudowanego. Dzięki przekształceniom 2D możemy użyć funkcji translate(), aby odtworzyć to zachowanie.

Możemy połączyć je z animacją DOM, aby uzyskać najlepszą możliwą jakość obrazu.

<div style="position:relative; height:120px;" class="hwaccel">

  <div style="padding:5px; width:100px; height:100px; background:papayaWhip;
              position:absolute;" id="box">
  </div>
</div>

<script>
document.querySelector('#box').addEventListener('click', moveIt, false);

function moveIt(evt) {
  var elem = evt.target;

  if (Modernizr.csstransforms && Modernizr.csstransitions) {
    // vendor prefixes omitted here for brevity
    elem.style.transition = 'all 3s ease-out';
    elem.style.transform = 'translateX(600px)';

  } else {
    // if an older browser, fall back to jQuery animate
    jQuery(elem).animate({ 'left': '600px'}, 3000);
  }
}
</script>

W narzędziu Modernizr testujemy cechy przekształceń 2D i przejścia CSS w CSS. Jeśli tak, będziemy korzystać z funkcji tłumaczenia, aby przesunąć pozycję. Jeśli jest to animowane podczas przejścia, istnieje duże prawdopodobieństwo, że przeglądarka może przyspieszyć ten proces. Aby ponownie dać przeglądarce ruch we właściwym kierunku, użyjemy powyższego „magicznego punktu CSS”.

Jeśli nasza przeglądarka nie ma tak dużych możliwości, użyjemy jQuery, aby przenieść element. Możesz pobrać wtyczkę jQuery Transform polyfill autorstwa Louisa-Remi Babe'a, aby zrobić to automatycznie.

window.requestAnimationFrame

Usługa requestAnimationFrame została opracowana przez Mozilla i wprowadzona przez WebKit w celu zapewnienia natywnego interfejsu API do uruchamiania animacji, zarówno opartych na DOM/CSS, jak i w technologii <canvas> lub WebGL. Przeglądarka może optymalizować równoczesne animacje w ramach pojedynczego cyklu przeformatowania i ponownego renderowania, co przekłada się na większą dokładność animacji. Na przykład animacje oparte na języku JS zsynchronizowane z przejściami CSS lub SVG SMIL. Poza tym jeśli pętlę animacji przeprowadzasz na niewidocznej karcie, przeglądarka nie będzie jej działać, co oznacza mniejsze zużycie procesora, GPU i pamięci, co znacznie wydłuży czas pracy na baterii.

Aby dowiedzieć się, jak i dlaczego warto korzystać z funkcji requestAnimationFrame, obejrzyj artykuł Paula Irlandii o requestAnimationFrame for smart animating.

Profilowanie

Gdy odkryjesz, że można przyspieszyć działanie aplikacji, czas przyjrzeć się profilowaniu, aby dowiedzieć się, jakie optymalizacje mogą przynieść największe korzyści. Optymalizacje często mają negatywny wpływ na łatwość obsługi kodu źródłowego, dlatego należy je stosować tylko w razie potrzeby. Profilowanie informuje, które części kodu przyniosłyby największe korzyści przy poprawie ich wydajności.

Profilowanie JavaScript

Programy profilujące JavaScript pozwalają ocenić wydajność aplikacji na poziomie funkcji JavaScriptu, mierząc czas potrzebny na wykonanie poszczególnych funkcji od początku do końca.

Łączny czas wykonywania funkcji to łączny czas potrzebny na wykonanie jej od góry do dołu. Czas wykonywania netto to łączny czas wykonania brutto pomniejszony o czas, który upłynął na wykonanie funkcji wywoływanych przez funkcję.

Niektóre funkcje są wywoływane częściej niż inne. Programy profilujące zwykle podają czas potrzebny na uruchomienie wszystkich wywołań, a także średni, minimalny i maksymalny czas wykonywania.

Więcej informacji znajdziesz w dokumentacji dotyczącej profilowania w Narzędziach deweloperskich w Chrome.

DOM

Wydajność JavaScriptu ma duży wpływ na płynność i responsywność aplikacji. Trzeba pamiętać, że chociaż programy profilujące JavaScript mierzą czas wykonywania kodu JavaScript, to także pośrednio mierzą czas spędzony na operacjach DOM. Te operacje DOM często są podstawą problemów z wydajnością.

function drawArray(array) {
  for(var i = 0; i < array.length; i++) {
    document.getElementById('test').innerHTML += array[i]; // No good :(
  }
}

Np. w powyższym kodzie prawie nie ma czasu na wykonywanie rzeczywistego JavaScriptu. Jest bardzo prawdopodobne, że funkcja „drapuj” pojawi się w Twoich profilach, ponieważ w sposób niecelowy oddziałuje na model DOM.

Porady i wskazówki

Funkcje anonimowe

Funkcje anonimowe nie są łatwe do profilowania, ponieważ z natury nie mają nazwy, pod którą mogłyby być wyświetlane w programie profilującym. Można to obejść na 2 sposoby:

$('.stuff').each(function() { ... });

przepisz do:

$('.stuff').each(function workOnStuff() { ... });

Nie jest powszechnie znane, że JavaScript obsługuje wyrażenia funkcji nadawania nazw. Dzięki temu będą one doskonale widoczne w narzędziu profilującym. Z tym rozwiązaniem jest jeden problem: wyrażenie nazwane powoduje umieszczenie nazwy funkcji w bieżącym zakresie leksykańskim. Zachowaj ostrożność, ponieważ może to zasłaniać inne symbole.

Profilowanie długich funkcji

Wyobraź sobie, że masz długą funkcję i podejrzewasz, że tylko niewielka ich część może być przyczyną problemów z wydajnością. Możesz to zrobić na 2 sposoby:

  1. Prawidłowa metoda: refaktoryzuj kod tak, aby nie zawierał żadnych długich funkcji.
  2. Metoda „zła robienie rzeczy”: dodaj do kodu instrukcje w postaci nazwanych funkcji samoczynnych. Jeśli zachowasz ostrożność, nie zmieni to semantyki i sprawi, że fragmenty funkcji będą się wyświetlać w programie profilu jako pojedyncze funkcje: js function myLongFunction() { ... (function doAPartOfTheWork() { ... })(); ... } Nie zapomnij usunąć tych dodatkowych funkcji po zakończeniu profilowania. Możesz też wykorzystać je jako punkt wyjścia do refaktoryzacji kodu.

Profilowanie DOM

Najnowsze narzędzia dla programistów Chrome Web Inspektora obejmują nowy widok Oś czasu, który pokazuje oś czasu działań niskiego poziomu wykonanych przez przeglądarkę. Możesz użyć tych informacji do optymalizacji operacji DOM. Postaraj się zmniejszyć liczbę „działań”, które przeglądarka musi wykonywać podczas wykonywania kodu.

Widok osi czasu może zawierać mnóstwo informacji. Postaraj się więc ograniczyć liczbę przypadków testowych do wykonania niezależnie.

Profilowanie DOM

Na ilustracji powyżej widać dane wyjściowe widoku osi czasu dla bardzo prostego skryptu. W panelu po lewej stronie wyświetlane są operacje wykonywane przez przeglądarkę w kolejności przewlekłej, a na osi czasu w panelu po prawej – rzeczywisty czas wykorzystywany przez poszczególne operacje.

Więcej informacji o widoku osi czasu Alternatywnym narzędziem do profilowania w Internet Explorerze jest DynaTrace Ajax Edition.

Strategie profilowania

Wyróżnianie aspektów

Jeśli chcesz profilować aplikację, spróbuj zidentyfikować te aspekty jej funkcjonalności, które mogą powodować spowolnienie. Następnie spróbuj uruchomić profil, który będzie wykonywał tylko te fragmenty kodu, które mają zastosowanie do tych aspektów aplikacji. Ułatwi to interpretację danych profilowania, ponieważ nie będą one mieszane ze ścieżkami kodu, które nie są związane z rzeczywistym problemem. Dobrymi przykładami poszczególnych aspektów aplikacji mogą być:

  1. Czas uruchamiania (aktywuj program profilujący, załaduj ponownie aplikację, poczekaj na zakończenie inicjowania, zatrzymaj program profilujący).
  2. Kliknij przycisk i kolejną animację (uruchom program profilujący, kliknij przycisk, poczekaj na zakończenie animacji, zatrzymaj program profilujący).
Profilowanie GUI

Wykonanie tylko odpowiedniej części aplikacji w programie GUI może być trudniejsze niż w przypadku optymalizacji, np. w przypadku śledzenia promieni w silniku 3D. Przykładowo gdy chcesz profilować zdarzenia po kliknięciu przycisku, możesz wywołać niepowiązane z nimi zdarzenia przesunięcia kursora myszy, co sprawi, że wyniki będą mniej miarodajne. Spróbuj tego uniknąć :)

Interfejs programistyczny

Debuger został aktywowany za pomocą interfejsu programistycznego. Dzięki temu masz precyzyjną kontrolę nad tym, kiedy rozpoczyna się i kończy profilowanie.

Rozpocznij profilowanie od:

console.profile()

Zatrzymaj profilowanie za pomocą:

console.profileEnd()

Powtarzalność

Dzięki profilowaniu będziesz mieć pewność, że uda Ci się faktycznie odtworzyć uzyskane wyniki. Dopiero wtedy będzie można określić, czy optymalizacja faktycznie przyniosła poprawę. Profilowanie na poziomie funkcji jest też wykonywane w kontekście całego komputera. Nie jest to nauka ścisła. Na uruchomienia profilu osobistego może mieć wpływ wiele innych rzeczy dziejących się na komputerze:

  1. Niepowiązany licznik czasu w Twojej aplikacji, który uruchamia się, gdy mierzysz coś innego.
  2. Wykonuje swoją pracę.
  3. Inna karta w przeglądarce wykonuje ciężką pracę w tym samym wątku operacyjnym.
  4. Inny program na Twoim komputerze obciąża procesor, co spowalnia działanie Twojej aplikacji.
  5. Nagłe zmiany w polu grawitacyjnym Ziemi.

Warto również wielokrotnie wykonywać tę samą ścieżkę kodu w ramach jednej sesji profilowania. W ten sposób zmniejszysz wpływ powyższych czynników, a powolne fragmenty filmu mogą się jeszcze wyraźniej wyróżnić.

Mierzenie, ulepszanie i mierzenie

Po zidentyfikowaniu powolnego miejsca w programie zastanów się, jak ulepszyć jego działanie. Po zmianie kodu ponownie załóż profil. Jeśli wyniki są satysfakcjonujące, przejdź dalej. Jeśli nie zauważasz poprawy, prawdopodobnie wycofaj zmianę i nie pozostawiaj jej w trybie „ponieważ nie zaszkodzi”.

Strategie optymalizacji

Minimalizuj interakcję DOM

Typowym motywem dla przyspieszania działania aplikacji klienckich internetowych jest zminimalizowanie interakcji DOM. Choć szybkość działania silników JavaScript wzrosła o rząd wielkości, dostęp do DOM nie nastąpił z jednakową szybkością. Dzieje się tak również z bardzo praktycznych powodów, aby nigdy nie dojść do sytuacji (np. układanie stron i rysowanie ich na ekranie wymaga czasu).

Buforowanie węzłów DOM

Za każdym razem, gdy pobierasz węzeł lub listę węzłów z modelu DOM, zastanów się, czy będzie można ich użyć ponownie w późniejszych obliczeniach (lub nawet przy kolejnej iteracji pętli). Często jest tak, o ile nie dodajesz ani nie usuwasz węzłów w odpowiednim obszarze.

Przed:

function getElements() {
  return $('.my-class');
}

Po:

var cachedElements;
function getElements() {
  if (cachedElements) {
    return cachedElements;
  }
  cachedElements = $('.my-class');
  return cachedElements;
}

Buforuj wartości atrybutów

W ten sam sposób możesz buforować węzły DOM. Możesz też zapisywać w pamięci podręcznej wartości atrybutów. Wyobraź sobie, że animujesz atrybut stylu węzła. Jeśli wiesz, że tylko Ty (jak w tej części kodu) będziesz mieć kontakt z tym atrybutem, możesz zapisać jego ostatnią wartość w pamięci podręcznej przy każdej iteracji, aby nie musieć odczytywać go wielokrotnie.

Przed:

setInterval(function() {
  var ele = $('#element');
  var left = parseInt(ele.css('left'), 10);
  ele.css('left', (left + 5) + 'px');
}, 1000 / 30);

Po: js var ele = $('#element'); var left = parseInt(ele.css('left'), 10); setInterval(function() { left += 5; ele.css('left', left + 'px'); }, 1000 / 30);

Przenieś z pętli manipulacji DOM

Pętle są często najważniejszymi punktami optymalizacji. Pomyśl o sposobach na oddzielenie rzeczywistego załamania liczb od pracy z DOM. Często można wykonać obliczenia, a następnie zastosować wszystkie wyniki za jednym razem.

Przed:

document.getElementById('target').innerHTML = '';
for(var i = 0; i < array.length; i++) {
  var val = doSomething(array[i]);
  document.getElementById('target').innerHTML += val;
}

Po:

var stringBuilder = [];
for(var i = 0; i < array.length; i++) {
  var val = doSomething(array[i]);
  stringBuilder.push(val);
}
document.getElementById('target').innerHTML = stringBuilder.join('');

Ponowne rysowanie i przeformatowanie

Jak już wspomnieliśmy, dostęp do DOM jest stosunkowo powolny. Kod działa bardzo wolno, gdy odczytuje wartość, która musi zostać przeliczona, ponieważ Twój kod ostatnio zmodyfikował coś związanego z DOM. Dlatego należy unikać mieszania dostępu do DOM z możliwością odczytu i zapisu. Kod powinien być zawsze podzielony na 2 etapy:

  • Etap 1. Odczytuj wartości DOM niezbędne dla Twojego kodu
  • Etap 2. Zmodyfikuj DOM

Nie programuj wzorca takiego jak:

  • Etap 1. Odczyt wartości DOM
  • Etap 2. Zmodyfikuj DOM
  • Etap 3. Poczytaj więcej
  • Etap 4. Zmodyfikuj DOM w innym miejscu.

Przed:

function paintSlow() {
  var left1 = $('#thing1').css('left');
  $('#otherThing1').css('left', left);
  var left2 = $('#thing2').css('left');
  $('#otherThing2').css('left', left);
}

Po:

function paintFast() {
  var left1 = $('#thing1').css('left');
  var left2 = $('#thing2').css('left');
  $('#otherThing1').css('left', left);
  $('#otherThing2').css('left', left);
}

Tę poradę należy uwzględnić w przypadku działań wykonywanych w jednym kontekście wykonywania JavaScriptu. (np. w module obsługi zdarzeń, w module obsługi interwałów lub podczas obsługi odpowiedzi ajax).

Wykonanie powyższej funkcji paintSlow() powoduje utworzenie tego obrazu:

paintSlow()

Jeśli przełączysz się na szybszą implementację, wyświetli się ten obraz:

Szybsza implementacja

Te obrazy pokazują, że zmiana sposobu dostępu kodu do DOM może znacznie zwiększyć wydajność renderowania. W takim przypadku oryginalny kod musi ponownie obliczyć style i układ strony dwukrotnie, aby uzyskać ten sam wynik. Podobną optymalizację można zastosować do całego kodu „realistycznego”, uzyskując naprawdę niesamowite wyniki.

Więcej informacji: Rendering: repaint, reflow/relayout, restyle Stoyan Stefanov

Ponowne rysowanie i pętla zdarzeń

Wykonywanie JavaScriptu w przeglądarce odbywa się zgodnie z modelem „Pętla zdarzeń”. Domyślnie przeglądarka jest w stanie „nieaktywny”. Działanie tego stanu może być zakłócone przez zdarzenia pochodzące z interakcji użytkownika lub takie jak liczniki czasu JavaScriptu czy wywołania zwrotne Ajax. Za każdym razem, gdy fragment kodu JavaScript jest uruchomiony w takim punkcie przerwania, przeglądarka zwykle czeka na jego zakończenie, aż ponownie wyrenderuje ekran (mogą występować wyjątki w przypadku bardzo długo działających skryptów JavaScript lub takich jak pola alertów, które skutecznie przerywają wykonywanie kodu JavaScript).

Konsekwencje

  1. Jeśli wykonanie Twoich cykli animacji JavaScript trwa dłużej niż 1/30 sekund, nie będziesz mieć możliwości tworzenia płynnych animacji, ponieważ przeglądarka nie będzie ponownie malowana podczas wykonywania kodu JS. Jeśli zamierzasz obsługiwać zdarzenia użytkownika, musisz zrobić to znacznie szybciej.
  2. Czasami warto opóźnić niektóre działania JavaScriptu trochę później. Przykład: setTimeout(function() { ... }, 0) Informuje to przeglądarkę, że ma wykonać wywołanie zwrotne, gdy tylko pętla zdarzeń będzie ponownie bezczynna (niektóre przeglądarki będą czekały co najmniej 10 ms). Pamiętaj, że spowoduje to utworzenie 2 cykli wykonywania JavaScriptu, które będą bardzo zbieżne w czasie. Obydwa elementy mogą spowodować ponowne wyrenderowanie ekranu, co może podwoić łączny czas poświęcany na malowanie. To, czy spowoduje to wywołanie dwóch wyrenderowań, zależy od algorytmu heurystycznego w przeglądarce.

Wersja standardowa:

function paintFast() {
  var height1 = $('#thing1').css('height');
  var height2 = $('#thing2').css('height');
  $('#otherThing1').css('height', '20px');
  $('#otherThing2').css('height', '20px');
}
Ponowne rysowanie i pętla zdarzeń

Dodajmy trochę opóźnienia:

function paintALittleLater() {
  var height1 = $('#thing1').css('height');
  var height2 = $('#thing2').css('height');
  $('#otherThing1').css('height', '20px');
  setTimeout(function() {
    $('#otherThing2').css('height', '20px');
  }, 10)
}
Opóźnienie

W opóźnionej wersji widać, że przeglądarka wyświetla obraz dwa razy, ale 2 zmiany na stronie to tylko 1/100 sekundy.

Leniwe inicjowanie

Użytkownicy oczekują aplikacji internetowych, które szybko się ładują i elastycznie. Jednak użytkownicy mają różne progi dotyczące tego, co postrzegają jako powolne działanie, w zależności od wykonywanej przez siebie czynności. Na przykład aplikacja nie powinna wykonywać wielu zadań po przesunięciu kursora myszy, ponieważ może to negatywnie wpłynąć na komfort korzystania z aplikacji, gdy użytkownik nadal będzie poruszać myszą. Użytkownicy są jednak przyzwyczajeni do zaakceptowania niewielkiego opóźnienia po kliknięciu przycisku.

Dlatego warto jak najpóźniej przesunąć kod inicjowania (np. gdy użytkownik kliknie przycisk aktywujący określony składnik aplikacji).

Przed: js var things = $('.ele > .other * div.className'); $('#button').click(function() { things.show() });

Po: js $('#button').click(function() { $('.ele > .other * div.className').show() });

Przekazywanie dostępu do wydarzenia

Rozmieszczenie modułów obsługi zdarzeń na stronie może zająć stosunkowo dużo czasu, a także być pracochłonne, gdy elementy są zastępowane dynamicznie, co wymaga ponownego dołączenia modułów obsługi zdarzeń do nowych elementów.

Rozwiązaniem w tym przypadku jest użycie techniki nazywanej przekazywaniem zdarzeń. Zamiast dołączać do elementów poszczególne moduły obsługi zdarzeń, mamy do czynienia z dymacyjnym charakterem wielu zdarzeń przeglądarki. W tym celu dołączasz moduł obsługi zdarzeń do węzła nadrzędnego i sprawdzasz węzeł docelowy zdarzenia w celu sprawdzenia, czy to zdarzenie może Cię zainteresować.

W języku jQuery można to łatwo wyrazić w następujący sposób:

$('#parentNode').delegate('.button', 'click', function() { ... });

Kiedy nie używać przekazywania dostępu do zdarzeń

Czasami może być odwrotnie: gdy używasz przekazywania zdarzeń i masz problemy z wydajnością. Zasadniczo delegowanie zdarzeń zapewnia czas inicjowania o stałym złożoności. Jednak za każde jego wywołanie trzeba zapłacić opłatę za sprawdzenie, czy dane wydarzenie jest interesujące. Może to być kosztowne, zwłaszcza w przypadku wydarzeń, które często mają miejsce, np. „mouseover” czy nawet „mousemove”.

Typowe problemy i ich rozwiązania

To, co robię w $(document).ready, zabiera mi dużo czasu

Osobista rada Malte: nigdy nie rób niczego w $(document).ready. Postaraj się dostarczyć dokument w ostatecznej postaci. OK, możesz rejestrować detektory zdarzeń, ale tylko za pomocą selektora identyfikatora lub przekazywania dostępu do zdarzeń. W przypadku kosztownych zdarzeń, takich jak „mousemove”, opóźnij rejestrację do momentu, aż będą potrzebne (wydarzenie najeżdżania kursorem na odpowiedni element).

Jeśli naprawdę chcesz coś zrobić, np. wysłać żądanie Ajax, by uzyskać rzeczywiste dane, możesz pokazać animację. Możesz ją też dodać jako identyfikator URI danych, jeśli jest to animowany GIF itp.

Od momentu dodania do strony filmu Flash wszystko działa bardzo wolno

Dodanie Flasha do strony zawsze spowalnia renderowanie, ponieważ ostateczny układ okna musi być „negocjowany” między przeglądarką a wtyczką Flash. Jeśli nie uda Ci się w pełni uniknąć umieszczenia kodu Flash na Twoich stronach, ustaw parametr Flash „wmode” na wartość „window” (czyli domyślną). Wyłącza to możliwość tworzenia złożonych elementów HTML i Flash (elementy HTML umieszczone nad filmem Flash nie będą przezroczyste). Może to być niedogodność, ale może znacznie zwiększyć skuteczność Twoich reklam. Zobacz na przykład, w jaki sposób strona youtube.com ostrożnie unikała umieszczania warstw nad głównym odtwarzaczem.

Zapisuję rzeczy w localStorage, a aplikacja się zacina

Zapisywanie w pamięci lokalnej to operacje synchroniczne, które polegają na zwiększeniu ilości dysku twardego. „Długo trwające” operacje synchroniczne podczas animacji nie są konieczne. Przenieś dostęp do lokalnego miejsca na dane w miejsce w kodzie, w którym masz pewność, że użytkownik jest nieaktywny i nie pojawiają się żadne animacje.

Profilowanie wskazuje bardzo wolny selektor jQuery

Najpierw sprawdź, czy selektor można uruchomić za pomocą funkcji document.querySelectorAll. Możesz to sprawdzić w konsoli JavaScript. Jeśli istnieje wyjątek, zmodyfikuj selektor tak, aby nie używać żadnego specjalnego rozszerzenia z platformy JavaScript. Przyspieszy to selektor w nowoczesnych przeglądarkach o określony rząd wielkości.

Jeśli to nie pomoże lub chcesz też działać szybko w nowoczesnych przeglądarkach, postępuj zgodnie z tymi wskazówkami:

  • Postaraj się podać jak najbardziej szczegółowe informacje po prawej stronie selektora.
  • W skrajnej prawej części selektora użyj nazwy tagu, której rzadko używasz.
  • Jeśli nic nie pomoże, pomyśl o przeredagowaniu tekstu, aby móc skorzystać z selektora identyfikatora.

Wszystkie te manipulacje DOM zajmują dużo czasu

Wstawianie, usuwanie i aktualizowanie węzłów DOM może działać bardzo wolno. Zwykle można to zoptymalizować, generując duży ciąg kodu HTML i używając tagu domNode.innerHTML = newHTML do zastąpienia starej treści. Pamiętaj, że może to utrudniać konserwację, a także tworzyć linki do pamięci w IE, więc zachowaj ostrożność.

Innym częstym problemem jest to, że kod inicjujący może tworzyć dużo kodu HTML. Przykładem może być wtyczka jQuery, która przekształca pole wyboru w elementy div, bo tego właśnie oczekują użytkownicy, ignorując sprawdzone metody UX. Jeśli zależy Ci na szybkim działaniu strony, nigdy jej nie rób. Zamiast tego prześlij wszystkie znaczniki po stronie serwera w ich ostatecznej postaci. Jest tu też wiele problemów, więc dobrze się zastanów, czy warto używać szybkości.

Narzędzia,

  1. JSPerf – sprawdzanie fragmentów kodu JavaScript
  2. Firebug – profilowanie w Firefoksie
  3. Narzędzia dla programistów Google Chrome (dostępne jako WebInspector w Safari)
  4. DOM Monster – optymalizacja wydajności DOM
  5. DynaTrace Ajax Edition – do profilowania i optymalizacji malowania w Internet Explorerze

Więcej informacji

  1. Szybkość działania Google
  2. Paul Ireland o wydajności jQuery
  3. Ekstremalna wydajność JavaScriptu (prezentacja)