Wypróbuj

Nozbe - get things done

i zwiększ swoją produktywność

Asynchroniczne ładowanie skryptów JavaScript

2

Jednym ze sposobów na przyspieszenie wczytywania strony internetowej jest asynchroniczne ładowanie skryptów JavaScript. Jest to ważne szczególnie kiedy wykorzystujemy na naszych stronach skrypty znajdujące się na zdalnych serwerach, ponieważ często ich pobieranie trwa długo. Przeglądarki internetowe, domyślnie podczas wczytywania strony przetwarzają poszczególne elementy DOM jeden po drugim czyli synchronicznie. Jako, że script również jest takim elementem, jego przedłużone wczytywanie może zatrzymać przetwarzanie pozostałych elementów. Na szczęście istnieją sposoby, żeby zmusić przeglądarkę do ładowania skryptów JavaScript asynchronicznie i tym tematem zajmę się w dzisiejszym wpisie. Zanim jednak do tego przejdziemy, spójrzmy na przykład, który posłuży nam jako punkt wyjścia do dalszych rozważań:

<!DOCTYPE html>
<html>
    <head>
        <script src="http://code.jquery.com/jquery-1.11.1.min.js"></script>
    </head>
    <body>
        <h1>Asynchroniczne ładowanie skryptów JavaScript</h1>
        <img src="http://devget.net/wp-content/uploads/2014/05/html-css-js.png">
    </body>
</html>

W powyższym dokumencie HTML, w jego sekcji head widzimy standardowy sposób na deklarację skryptu JavaScript – pobieramy w tym przypadku bibliotekę jQuery, znajdującą się na zdalnym serwerze. Następnie w sekcji body znajduje się obrazek, który również pobierany jest ze zdalnego serwera.

Jeśli wczytamy taki dokument HTML i podejrzymy ruch sieciowy podczas ładowania strony za pomocą wtyczki Firebug możemy stwierdzić, że rzeczywiście obrazek wczytywany jest dopiero kiedy zakończy się pobieranie pliku JavaScript:

firebug przykład pierwszy

Jak więc spowodować aby przeglądarka nie czekała na pobranie skryptu JS ze zdalnego serwera? O tym w kolejnych akapitach.

Podejście old school’owe

Na początek przyjrzymy się podejściu, które stosowane było zanim przeglądarki internetowe zaczęły porządnie wspierać standardy HTML5. Wzorzec ten nazywany jest wstrzykiwaniem skryptu i… jest nim dosłownie. Zresztą sami zobaczcie:

<!DOCTYPE html>
<html>
	<head>
		<script>
			var script = document.createElement('script');
			script.src = "http://code.jquery.com/jquery-1.11.1.min.js";
			document.getElementsByTagName('head')[0].appendChild(script);
		</script>
	</head>
	<body>
		<h1>Asynchroniczne ładowanie skryptów JavaScript</h1>
		<img src="http://devget.net/wp-content/uploads/2014/05/html-css-js.png">
	</body>
</html>

Jak widzicie, tym razem w sekcji head mamy deklarację skryptu „inline”. Nie robi on nic innego jak tworzy element script, a następnie wstrzykuje go do elementu head… czyli w zasadzie poprzedni przykład tylko „na około”. Dzieje się tak ponieważ, skrypty tworzone i dodawane dynamicznie do strony internetowej są domyślnie asynchroniczne – nie blokują one ładowania pozostałych elementów strony, zresztą poniżej dowód:

firebug przykład drugi

Wzorzec ten nie jest wolny od wad. Jako, że przeglądarka nie wie co dokładnie wydarzy się w skrypcie „inline”, musi zablokować ładowanie stylów CSS do czasu zakończenia wykonywania skryptu (to dlatego, że JavaScript ma możliwość manipulowania stylami), warto więc taką sekcję umieszczać przed deklaracją stylów CSS.

Opisane rozwiązanie jest już obecnie przestarzałe ponieważ, praktycznie wszystkie nowoczesne przeglądarki implementują rozwiązanie przewidziane w specyfikacji HTML5. O tym w kolejnym punkcie.

Asynchroniczne ładowanie skryptów – podejście z HTML5

Tak jak wspomniałem, w HTML5 asynchroniczne przetwarzanie skryptów dostępne jest standardowo i można je uzyskać bardzo prosto – wystarczy skorzystać z atrybutu async:

<!DOCTYPE html>
<html>
	<head>
		<script async src="http://code.jquery.com/jquery-1.11.1.min.js"></script>
	</head>
	<body>
		<h1>Asynchroniczne ładowanie skryptów JavaScript</h1>
		<img src="http://devget.net/wp-content/uploads/2014/05/html-css-js.png">
	</body>
</html>

Pod tym linkiem możecie sprawdzić kompatybilność tego rozwiązania przeglądarkami – jak widzicie, obecnie tylko Opera Mini nie potrafi obsłużyć tego atrybutu…

Problemem w opisanym podejściu może być fakt, że wykorzystując atrybut async nie mamy gwarancji kolejności wykonania skryptów. Jeśli więc między wczytywanymi plikami istnieją zależności, możliwe że będziemy musieli rozważyć użycie biblioteki takiej jak require.js do ładowania skryptów… (istnieje niby atrybut defer, jednak jest on słabo obsługiwany przez przeglądarki i nie zaleca się jego wykorzystania).

Dla porządku jeszcze dowód na to, że atrybut async również działa:

firebug przykład trzeci

Podsumowanie

Oczywiście, opisane sposoby nie są jedynymi na uzyskanie pożądanego efektu. Powstało mnóstwo bibliotek/wtyczek ułatwiających asynchroniczne wczytywanie skryptów (na przykład script.js), również w jQuery istnieje odpowiednia metoda ($.getScript). Moim zamierzeniem było raczej zwrócenie uwagi na asynchroniczne ładowanie skryptów za pomocą sposobu dostępnego w HTML5 i jego zalety i wady.

CHCESZ DARMOWEGO E-BOOKA?

Jeśli chcesz otrzymać mojego e-booka: Rozmowa Kwalifikacyjna - pytania z podstaw JavaScript zostaw mi swój e-mail:

Oprócz tego co poniedziałek dostaniesz maila z listą moich wpisów z poprzedniego tygodnia!

  • Chciałem skorzystać z atrybutu async, ale wszystko w łeb wzięło, bo ten mój kod nie był taki prosty do znalezienia. Tj. w kodzie źródłowym był pięknie przedstawiony, jak na obrazkach wyżej. Tylko w plikach już taki ładny nie był i nie wiedziałem, gdzie to async wrzucić.
    Słowem: no, nie znam się niestety na tym.

    Jednakże… Dzięki Tobie zobaczyłem, że te skrypty mogę wyrzucić całkowicie (wcześniej nie wiedziałem tylko jak tego dokonać). Blokowały mi renderowanie, a mam małego bzika na punkcie optymalizacji. 😉 Wyrzuciłem linijki, które odpowiadały za pakowanie skryptów na stronę i gra gitara.

    Mam pytanie co do Opery Mini i jej niezdolności do obsługi atrybutu async – co się stanie, jeśli przyjdzie jej wczytywać takie skrypty? Czegoś nie wczyta? Zostawi pustą stronę? Zbzikuje?

    Pozdrawiam,
    Łukasz

    • Bartłomiej Dybowski

      Opera mini pominie parametr async i zachowa się tak jak by go nie było czyli wczyta skrypt blokując wczytywanie DOM na czas ładowania. / jeśli się mylę to mnie poprawcie 😉 /