Optymalizacja JavaScriptów

Podczas mojej pracy z javascriptem musiałam zwracać dużą uwagę na optymalizację kodu i często refaktorować stary. Zoptymalizowany, dobrze napisany kod szybciej się wykonuje, zajmuje mniej linii, jest bardziej czytelny i łatwiej nad nim pracować innym programistom. No i ma wcale niemałe znaczenie dla wydajności strony.

W poniższych punktach zebrałam zestaw wytycznych optymalizacyjnych. Punkty powstały dzięki mojej wiedzy zdobytej z doświadczenia w pracy front-end deva, książek, testów, eksperymentów i szkoleń.

  • Zmniejsz odwołania do DOM. Przypisuj referencje DOM do lokalnych zmiennych i odwołuj się do nich przez te zmienne. Uzyskanie dostępu do DOM jest kosztowne i bardzo często stanowi główne źródło problemów z wydajnością w javascripcie – czyli:
zamiast:
var 
    $list = $('.mod ul.fruits');

$('.mod .body > p').text();
$('.mod').addClass('active');
zrobić: 
var 
    $mod = $('.mod'),
    $list = $mod.find('ul.fruits');
$mod.find('.body > p').text();
$mod.addClass('active');
  • Unikaj dostępu do DOM  w pętli.

Należy unikać pobierania lub ustawiania elementów DOM wewnątrz pętli – czyli nie robić takich rzeczy:

for( var i=0; i < 200; i++ ) {
    document.getElementById('result').innerHTML += '<p>- ' + i + '</p>'; 
}

Natomiast zawartość do podstawienia na stronie należy przechowywać podczas całej iteracji w zmiennej, a dopiero po zakończeniu pętli dodać element na stronę.

var i = 0, content;
for( var i=0; i < 200; i++ ) {
    content += '<p>- ' + i + '</p>'; 
}
document.getElementById('result').innerHTML += content;
  • Korzystaj z API selektorów CSS
document.querySelector("ul .active");
document.querySelectorAll("#container .top");
  • Do często modyfikowanych elementów uzyskuj dostęp przez atrybut id a nie klasę lub tag. Id działa najszybciej,  a element pobieramy za pomocą document.getElementById(id)
  • Stosuj w miarę możliwości zamiast ifów -> switche lub operator ternarny
      $zmienna = wyrażenie ? "zrób gdy true" : "zrób gdy false"
 
      // co jest to równoznaczne z zapisem zwykłej instrukcji warunkowej if...else...

      if( wyrażenie ){
          $zmienna = "to zrób gdy wyrażenie zwróci true";
      } else {
          $zmienna = "to zrób, gdy wyrażenie zwróci false";
      }
  •  Jeśli korzystasz z jQuery, wykorzystuj metody, które działają szybciej, np. zamiast .parents() stosuj .closest()
  •  Zapisuj obliczane wartości w zmiennych i na nich wykonuj dalsze działania. Jeśli stosujesz pętle for i odwołujesz się do długości tablicy, to nie pobieraj length za każdą iteracją tablicy poprzez
       for(var i=0; i<tab.length; i++)

…. tylko zapisz długość tablicy w zmiennej

       for(var i = 0, len = tab.length; i < len; i++ )

  • Zastąp fragment pętli >> i++  na >> i = i +  1 lub i += 1 – prosi o to JSLint
  • Używaj pętli odliczających do zera ( 0 )  – używają o jedną zmienną mniej ( brak max ) i łatwiej porównać do zera niż np do długości tablicy lub innej wartości.

       PĘTLA FOR

    var i, tab = [];
   
    for ( i = tab.length; i--){

    }

 

         PĘTLA WHILE

     var 
       tab = [],
        i = tab.length;

     while ( i--){
     ....
     }

 

  • Deklaruj zmienne na początku każdego zakresu danych, funkcji itp. w postaci bloku zmiennych z jednym słowem kluczowym var.
      var 
          $body = $('body'),
          bodyId = $body.attr('id');
  • Jeśli optymalizacja skryptu ma na celu przyspieszyć ładowanie strony – plik zewnętrzny ze skryptami umieść na końcu dokumentu, przed </body>, żeby nie blokował renderowania, łącz kilka plików JS w jeden, a jeśli skrypt ma być w headzie to staraj się grupować zagnieżdżone skrypty, tzn. 1 – nie mieszać na zmianę np. raz ładowania skryptu, raz stylów i znowu skryptu i stylów i skryptu zewnętrznego i zagnieżdżonego itp…tylko najpierw np załaduj style potem wszystkie skrypty – i najlepiej w jednym bloku <script> ..</script>.  Skrypty blokują progresywne pobieranie zawartości strony. Przeglądarki pobierają w jednym czasie kilka elementów, a kiedy napotkają na jakiś zewnętrzny skrypt, zatrzymują dalsze pobieranie, by go pobrać, przeanalizować i wykonać. Nie wpływa to dobrze na czas ładowania strony zwłaszcza jeśli tych skryptów zewnętrznych/bloków skryptów jest wiele. Najgorszym antywzorcem dołączania javascriptów jest zastosowanie kilku osobnych plików js na początku dokumentu:

 

        <!doctype html>
        <html>
        <head>
            <title>Moja strona</title>
            <!-- ANTYWZORZEC -->
            <script src="jQuery.js"></script>
            <script src="myapp.js"></script>
            <script src="scripts.js"></script>
        </head>
        <body>
        ......


Najlepiej jest połączyć kilka plików javascriptowych w jeden i dołączyć je do dokumentu na samym końcu strony.

        <!doctype html>
        <html>
        <head>
            <title>Moja strona</title>
        </head>
        <body>
        ......
            <script src="all_scripts.js"></script>
        </body>
        </html>
  • Ładuj inne skrypty zewnętrzne asynchronicznie ( nie działa we wszystkich przeglądarkach http://caniuse.com/#search=async), jeśli możesz, opóźniaj też ładowanie innych skryptów albo wczytuj je dynamicznie. Do opóźnienia ładowania możesz skorzystać z funkcji setTimeout(). Dzięki temu zapewnimy nieblokujące pobieranie skryptów na stronie – oczywiście nie wszystkie skrypty można tak wczytywać, bo na stronie mogą być na przykład jakiś pliki js dołączane tradycyjnie, które do działania wymagają elementów z dynamicznie wczytywanego pliku .js. Przy asynchronicznym wczytywaniu nie mamy gwarancji o kolejności i czasie w jakim zostanie załadowany plik, dlatego też skrypt wykonywany tuż po nim nie może zakładać istnienia definiowanych przez niego obiektów.

Dynamiczne dodawanie plików js polega na utworzeniu nowego elementu skryptu ( <script> ), ustawieniu jego atrybutu src oraz dołączenia do strony. Natomiast dla skryptów zagnieżdżonych  można zrobić obejście, które zaczyta je dopiero po załadowaniu głównego skryptu. Aby wyeliminować ryzyko o którym pisałam powyżej, najlepszym rozwiązaniem będzie zebranie wszystkich skryptów ze strony (również tych zagnieżdżonych) i wstawienie ich jako funkcji tablicy. Po pobraniu głównego pliku js przez przeglądarkę, skrypt główny wykona funkcje zebrane w tablicy. Wzorzec wczytuje dodatkowy kod JavaScript bezwarunkowo po załadowaniu strony WWW.

KROK 1.

Tworzymy tablicę ( jak najwyżej w kodzie strony ) przechowującą skrypty, które w tradycyjny sposób chcielibyśmy zagnieździć na stronie.

         var myScriptsSpaceName = {
             allScripts: []
         };

 

KROK 2.

Wszystkie pojedyncze skrypty otocz funkcjami i umieść jako elementy w tablicy scripts[];

        <script>
            myScriptsSpaceName.allScripts.push( function(){
                console.log('To jest kod javascript zagnieżdżony w html');
            });
        </script>

 

KROK 3.

Wszystkie zebrane w tablicy skrypty, główny skrypt powinien wykonać w pętli.

         var 
             i, 
             scripts = myScriptsSpaceName.allScripts, 
             max = scripts.length;
          
         for( i = 0; i < max; max += 1 ) {
           scripts[i];
         }

KROK 4.

Dodanie elementu <script> na stronie. Skrypt można dołączyć w sekcji <head>

    document.documentElement.firstChild.appendChild(script); >> LUB >>
    document.getElementsByTagName("head")[0].appendChild(script);

lub w dowolnym miejscu w ciele strony.

    document.body.appendChild(script);

W tym przypadku musi istnieć znacznik skryptu ( bo wykonujemy skrypt ). Możemy sprawdzić czy <script> tag istnieje

    var first_scriptTag = document.getElementsByTagName("script")[0];
    first_scriptTag.parentNode.insertBefore(newScript, first_scriptTag);

W zmiennej first_scriptTag przechowujemy element <script> – strona musi go posiadać. Zmienna newScript zawiera nowy element skryptu dodawany do strony.

  • Wywołuj skrypt tylko na żądanie – tylko wtedy, gdy jest nam rzeczywiście potrzebny. Po co pobierać kod ( np odpowiedzialny za wypełnienie zawartości zakładek po kliknięciu i przełączanie między nimi ), jeśli użytkownik nigdy nie przełączy zakładki?
  • Nie wstawiaj kodu js inlinowo ( tzn. w znacznikach html  – tj. onclick – ponieważ skrypty obsługi zdarzeń nie należą do warstwy prezentacji )
  • Nie wstawiaj/ustawiaj stylów za pomocą javaskryptu a jedynie dodawaj klasy ze stylami – chyba że javascript wylicza nam np pozycję elementu na stronie ( i np ustawiamy jsem css: position )
  • Unikaj animacji w jsie, do animacji wykorzystaj css3
  • Do wywoływania ciężkich funkcji operujących na DOM używaj requestAnimationFrame() zamiast setInterval() – pozwala uzyskać efekt 60 fps a to widać na ekranie 🙂
  • Unikaj layout thrashing, czyli naprzemiennego odczytywania/zapisywania do DOM. Ogólnie należy przeprowadzać jak najmniej aktualizacji DOM, a jeśli już to robimy, to przeprowadzać je w grupach, najlepiej poza aktualnie renderowanym drzewem węzłów. Możemy wykorzystać fragment dokumentu document.createDocumentFragment(); ) i dopiero gotowy fragment dokumentu  dodać do właściwego drzewa DOM dokumentu. Podczas dodawania fragmentu dokumentu do drzewa DOM dodawany jest nie fragment drzewa, a jego zawartość.
var p, txt, frag;

frag = document.createDocumentFragment();

p = document.createElement('p');
txt = document.createTextNode('przykładowy tekst');
p.appendChild(txt);

frag.appendChild(p);

p = document.createElement('p');
txt = document.createTextNode('przykładowy drugi tekst');
p.appendChild(txt);

frag.appendChild(p);

document.body.appendChild(frag);
  • Nie nadużywaj $(this) –  jquery – do niektórych właściwości elementów masz równie szybki dostęp za pomocą czystego javascript  this jak np. treści html itp. Nie musisz na obiekcie wywoływać metody do pobrania html
// nie wskazane
$('a').click(function(){
    var content = $(this).html();
});

// zalecane
$('a').click(function(){
    var content = this.innerHTML;
});
  • Dla funkcji z dużą ilością parametrów używaj obiektów konfiguracyjnych – przynajmniej nie będziesz musiał się trzymać sztywnej kolejności podawanych parametrów
  • Nie dodawaj funkcji anonimowych jako zdarzeń
  • Usuwaj zdarzenia, kiedy usuwasz widok/widgety z DOM
  • Nie deklaruj zmiennych wewnątrz instrukcji warunkowej tylko poza nią. Wewnątrz tylko operuj na tej zmiennej
  • Konwertuj liczby funkcją parseInt()
  • Używaj web workerów
  • Łącz wiele plików ze skryptami w jeden, minifikuj, kompresuj

 

A Wy dorzucilibyście coś do tego?

Dodaj komentarz