Optymalizuj wykonywanie JavaScriptu

Kod JavaScript często powoduje zmiany wizualne. Czasami jest to bezpośrednio przez manipulowanie stylem, a czasami przez obliczenia, które powodują zmiany wizualne, takie jak wyszukiwanie lub sortowanie danych. Złe ustawienie czasu lub długi czas działania kodu JavaScript to częste przyczyny problemów ze skutecznością. W miarę możliwości staraj się minimalizować wpływ tych zmian.

Kod JavaScript często powoduje zmiany wizualne. Czasami jest to bezpośrednio manipulowanie stylem, a czasami obliczenia, które powodują zmiany wizualne, takie jak wyszukiwanie lub sortowanie danych. Złe ustawienie lub długi czas wykonywania kodu JavaScript to częsta przyczyna problemów z wydajnością. W miarę możliwości staraj się minimalizować wpływ tych zmian.

Profilowanie wydajności kodu JavaScript może być sztuką, ponieważ napisany przez Ciebie kod JavaScript nie przypomina w ogóle kodu, który jest faktycznie wykonywany. Nowoczesne przeglądarki używają kompilatorów JIT oraz różnych optymalizacji i sztuczek, aby zapewnić możliwie najszybsze wykonanie kodu. W związku z tym dynamika kodu ulega znacznym zmianom.

Mimo to możesz wykonać kilka czynności, które z pewnością pomogą Twoim aplikacjom w skutecznym wykonywaniu kodu JavaScript.

Podsumowanie

  • Unikaj wywoływania metody setTimeout lub setInterval w celu aktualizacji wizualnych. Zamiast tego zawsze używaj metody requestAnimationFrame.
  • Przenoś długo działający kod JavaScript z wątku głównego do skryptów Web Worker.
  • Stosuj mikrozadania do wprowadzania zmian w DOM na przestrzeni kilku klatek.
  • Aby ocenić wpływ JavaScriptu, użyj osi czasu i profilowania JavaScriptu w Narzędziach deweloperskich w Chrome.

Użyj elementu requestAnimationFrame, aby wprowadzić zmiany wizualne.

Gdy na ekranie pojawiają się zmiany wizualne, chcesz, aby Twoja praca była wykonywana w odpowiednim czasie dla przeglądarki, czyli na samym początku kadru. Jedynym sposobem na zagwarantowanie, że kod JavaScript będzie uruchamiany na początku kadru, jest użycie requestAnimationFrame.

/**
    * If run as a requestAnimationFrame callback, this
    * will be run at the start of the frame.
    */
function updateScreen(time) {
    // Make visual updates here.
}

requestAnimationFrame(updateScreen);

Frameworki lub przykłady mogą używać funkcji setTimeout lub setInterval do wprowadzania zmian wizualnych, takich jak animacje. Problem polega na tym, że funkcja wywołania zwrotnego jest wykonywana w jakimś momencie w ramce, prawdopodobnie pod koniec, co często powoduje pominięcie klatki i więc zacięcia.

setTimeout powoduje, że przeglądarka pomija ramkę.

W zasadzie jQuery używała do tego celu funkcji setTimeout.animate W wersji 3 zmieniono go na requestAnimationFrame. Jeśli używasz starszej wersji jQuery, możesz zastosować do niej poprawkę, aby używać requestAnimationFrame. Jest to zdecydowanie zalecane.

Zmniejsz złożoność lub użyj Web Workers

Kod JavaScript jest uruchamiany w głównym wątku przeglądarki, obok obliczeń stylu, układu i w wielu przypadkach renderowania. Jeśli kod JavaScript działa przez długi czas, będzie blokować te inne zadania, co może spowodować pominięcie klatek.

Należy określić, kiedy i jak długo ma działać JavaScript. Jeśli np. używasz animacji, takiej jak przewijanie, czas wykonywania kodu JavaScript powinien wynosić w najlepszym przypadku 3–4 ms. Jeśli będziesz dłużej czekać, możesz stracić zbyt dużo czasu. Jeśli masz czas na bezczynność, możesz sobie pozwolić na większy luz.

W wielu przypadkach możesz przenieść czystą pracę obliczeniową do Web Workers, jeśli na przykład nie wymaga ona dostępu do DOM. Manipulowanie danymi lub ich przeszukiwanie, takie jak sortowanie czy wyszukiwanie, często dobrze pasuje do tego modelu, podobnie jak wczytywanie i generowanie modelu.

var dataSortWorker = new Worker("sort-worker.js");
dataSortWorker.postMesssage(dataToSort);

// The main thread is now free to continue working on other things...

dataSortWorker.addEventListener('message', function(evt) {
    var sortedData = evt.data;
    // Update data on screen...
});

Nie wszystkie zadania mogą być wykonywane w ramach tego modelu: zadania Web Worker nie mają dostępu do DOM. Jeśli Twoja praca musi być wykonywana w wątku głównym, rozważ użycie podejścia zbiorczego, w którym większe zadanie dzielisz na mikrozadania, z których każde zajmuje nie więcej niż kilka milisekund, a następnie uruchamiasz je w ramach obsługi requestAnimationFrame w każdej klatce.

Takie podejście ma konsekwencje w zakresie UX i UI. Musisz zadbać o to, aby użytkownik wiedział, że zadanie jest przetwarzane, np. za pomocą wskaźnika postępu lub aktywności. W każdym razie takie podejście pozwoli zachować wolny wątek główny aplikacji, dzięki czemu będzie ona lepiej reagować na interakcje z użytkownikiem.

Poznaj „podatek od ramki” w JavaScript

Podczas oceny frameworka, biblioteki lub własnego kodu należy sprawdzić, ile kosztuje wykonywanie kodu JavaScript na podstawie poszczególnych klatek. Jest to szczególnie ważne w przypadku animacji, przy których niezbędna jest wysoka wydajność, takich jak przejścia czy przewijanie.

Panel Wydajność w Narzędziach deweloperskich w Chrome to najlepszy sposób na pomiar kosztu kodu JavaScript. Zwykle otrzymujesz rekordy niskiego poziomu takie jak ten:

Rejestrowanie wydajności w Narzędziach deweloperskich w Chrome

W sekcji Główna znajduje się wykres płomienisty wywołań kodu JavaScript, dzięki któremu możesz dokładnie sprawdzić, które funkcje zostały wywołane i jak długo trwało ich wykonanie.

Dzięki tym informacjom możesz ocenić wpływ kodu JavaScript na wydajność aplikacji i zacząć znajdować oraz naprawiać problemy, które powodują zbyt długi czas wykonywania funkcji. Jak już wspomnieliśmy, należy usunąć długo działający kod JavaScript lub, jeśli to niemożliwe, przenieść go do Web Workera, aby zwolnić wątek główny i umożliwić kontynuowanie wykonywania innych zadań.

Aby dowiedzieć się, jak korzystać z panelu Wydajność, przeczytaj artykuł Pierwsze kroki w analizowaniu wydajności w czasie wykonywania.

Unikaj mikrooptymalizacji kodu JavaScriptu

Może być ciekawe, że przeglądarka może wykonać jedną wersję czegoś 100 razy szybciej niż inną, np. że żądanie offsetTop elementu jest szybsze niż obliczenie getBoundingClientRect(), ale prawie zawsze będziesz wywoływać takie funkcje tylko kilka razy na kadr, więc skupianie się na tym aspekcie wydajności kodu JavaScript jest zwykle stratą czasu. Zwykle oszczędzasz tylko ułamki milisekund.

Jeśli tworzysz grę lub aplikację wymagającą dużych zasobów obliczeniowych, prawdopodobnie nie dotyczy Cię ta wskazówka, ponieważ w takim przypadku zwykle musisz zmieścić wiele obliczeń w pojedynczym klatce, a w tej sytuacji wszystko się przydaje.

Krótko mówiąc, należy bardzo uważać na mikrooptymalizacje, ponieważ zwykle nie pasują one do rodzaju aplikacji, którą tworzysz.