Data publikacji: 31 grudnia 2013 r.
Dzięki JavaScript możemy modyfikować niemal każdy aspekt strony: treść, styl i reakcje na interakcje użytkownika. Jednak JavaScript może też blokować tworzenie DOM i opóźniać renderowanie strony. Aby zapewnić optymalną wydajność, użyj asynchronicznego JavaScriptu i usuń z ścieżki renderowania krytycznego wszystkie niepotrzebne elementy JavaScriptu.
Podsumowanie
- JavaScript może wysyłać zapytania do interfejsu DOM i CSSOM oraz je modyfikować.
- Bloki wykonywania kodu JavaScript w CSSOM.
- JavaScript blokuje tworzenie DOM, chyba że jest wyraźnie zadeklarowany jako asynchroniczny.
JavaScript to dynamiczny język, który działa w przeglądarce i pozwala nam zmieniać niemal każdy aspekt działania strony: możemy modyfikować zawartość przez dodawanie i usuwanie elementów z drzewa DOM, możemy modyfikować właściwości CSSOM każdego elementu, możemy obsługiwać dane wejściowe użytkownika i wiele innych rzeczy. Aby to zilustrować, zobacz, co się dzieje, gdy poprzedni przykład „Hello World” zostanie zmieniony tak, aby zawierał krótki skrypt wbudowany:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link href="style.css" rel="stylesheet" />
<title>Critical Path: Script</title>
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg" /></div>
<script>
var span = document.getElementsByTagName('span')[0];
span.textContent = 'interactive'; // change DOM text content
span.style.display = 'inline'; // change CSSOM property
// create a new element, style it, and append it to the DOM
var loadTime = document.createElement('div');
loadTime.textContent = 'You loaded this page on: ' + new Date();
loadTime.style.color = 'blue';
document.body.appendChild(loadTime);
</script>
</body>
</html>
Dzięki JavaScriptowi możemy uzyskać dostęp do modelu DOM i wyciągnąć odwołanie do ukrytego węzła span. Węzeł może nie być widoczny w drzewie renderowania, ale nadal jest obecny w modelu DOM. Gdy mamy już odwołanie, możemy zmienić tekst (za pomocą właściwości .textContent) i nawet zastąpić obliczoną właściwość stylu wyświetlania z wartości „brak” na „wstawiony”. Teraz na stronie wyświetla się komunikat „Cześć, interaktywni uczniowie”.
JavaScript umożliwia też tworzenie, stylizowanie, dodawanie i usuwanie nowych elementów w DOM. Teoretycznie cała strona może być tylko jednym dużym plikiem JavaScript, który tworzy i stylizuje elementy jeden po drugim. Chociaż to działa, w praktyce łatwiej jest używać HTML i CSS. W drugiej części funkcji JavaScript tworzymy nowy element div, ustawiamy jego zawartość tekstową, nadajemy mu styl i dołączamy do elementu body.
Zmieniliśmy treść i styl CSS dotychczasowego węzła DOM oraz dodaliśmy do dokumentu zupełnie nowy węzeł. Nasza strona nie zdobędzie żadnych nagród za design, ale pokazuje potencjał i elastyczność, jaką daje JavaScript.
Jednak mimo że JavaScript daje nam wiele możliwości, powoduje też wiele dodatkowych ograniczeń dotyczących sposobu i czasu renderowania strony.
Zwróć uwagę, że w poprzednim przykładzie skrypt wstawiany w tekście znajduje się u dołu strony. Dlaczego? Możesz to sprawdzić samodzielnie. Jeśli przeniesiemy skrypt nad element <span>
, zauważysz, że skrypt się nie powiedzie i poinformuje, że nie może znaleźć odwołania do żadnych elementów <span>
w dokumencie. Oznacza to, że getElementsByTagName('span')
zwraca null
. Pokazuje to ważną właściwość: nasz skrypt jest wykonywany dokładnie w miejscu, w którym został wstawiony do dokumentu. Gdy parsujący HTML napotyka tag skryptu, wstrzymuje proces tworzenia DOM i przekazuje kontrolę nad działaniem procesorowi JavaScript. Gdy proces JavaScript zakończy działanie, przeglądarka wznawia tworzenie DOM od miejsca, w którym zostało ono przerwane.
Innymi słowy, nasz blok skryptu nie może znaleźć żadnych elementów na stronie, ponieważ nie zostały one jeszcze przetworzone. Inaczej mówiąc: wykonanie skryptu wstawianego inline blokuje tworzenie modelu DOM, co opóźnia też początkowe renderowanie.
Kolejną subtelną właściwością wprowadzania skryptów na stronę jest to, że mogą one odczytywać i modyfikować nie tylko DOM, ale też właściwości CSSOM. W naszym przykładzie właśnie to robimy, gdy zmieniamy właściwość wyświetlania elementu span z wartości none na inline. Jaki jest efekt końcowy? Mamy teraz sytuację wyścigu.
Co się stanie, jeśli w momencie, gdy chcemy uruchomić skrypt, przeglądarka nie skończy pobierania i tworzenia obiektu CSSOM? Odpowiedź nie jest korzystna dla wydajności: przeglądarka opóźnia wykonanie skryptu i tworzenie elementu DOM, dopóki nie zakończy pobierania i tworzenia elementu CSSOM.
Krótko mówiąc, JavaScript wprowadza wiele nowych zależności między DOM, CSSOM i wykonywaniem JavaScriptu. Może to spowodować znaczne opóźnienia w przetwarzaniu i renderowaniu strony przez przeglądarkę:
- Lokalizacja skryptu w dokumencie ma znaczenie.
- Gdy przeglądarka napotka tag skryptu, tworzenie elementu DOM zostaje wstrzymane do czasu zakończenia działania skryptu.
- JavaScript może wysyłać zapytania do interfejsu DOM i CSSOM oraz je modyfikować.
- Wykonanie kodu JavaScript jest wstrzymywane, dopóki obiekt CSSOM nie będzie gotowy.
„Optymalizowanie ścieżki renderowania krytycznego” oznacza w dużej mierze zrozumienie i zoptymalizowanie grafu zależności między HTML, CSS i JavaScriptem.
Blokowanie przez parsowanie a JavaScript asynchroniczny
Domyślnie wykonanie JavaScriptu jest „blokujące dla parsowania”: gdy przeglądarka napotka skrypt w dokumencie, musi wstrzymać tworzenie DOM, przekazać kontrolę środowisku uruchomieniowemu JavaScriptu i pozwolić skryptowi na wykonanie, zanim kontynuuje tworzenie DOM. W poprzednim przykładzie pokazaliśmy, jak to działa w przypadku skryptu wbudowanego. Wbudowane skrypty zawsze blokują parsowanie, chyba że napiszesz dodatkowy kod, który opóźni ich wykonanie.
A co ze skryptami dołączonymi za pomocą tagu skryptu? Weź poprzedni przykład i wyodrębnij kod do oddzielnego pliku:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link href="style.css" rel="stylesheet" />
<title>Critical Path: Script External</title>
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg" /></div>
<script src="app.js"></script>
</body>
</html>
app.js
var span = document.getElementsByTagName('span')[0];
span.textContent = 'interactive'; // change DOM text content
span.style.display = 'inline'; // change CSSOM property
// create a new element, style it, and append it to the DOM
var loadTime = document.createElement('div');
loadTime.textContent = 'You loaded this page on: ' + new Date();
loadTime.style.color = 'blue';
document.body.appendChild(loadTime);
Niezależnie od tego, czy używasz tagu <script>, czy też inline kodu JavaScript, oczekujesz, że oba będą działać tak samo. W obu przypadkach przeglądarka wstrzymuje i wykonuje skrypt, zanim przetworzy resztę dokumentu. W przypadku zewnętrznego pliku JavaScript przeglądarka musi się zatrzymać, aby poczekać na pobranie skryptu z dysku, pamięci podręcznej lub zdalnego serwera. Może to spowodować opóźnienie od kilkudziesięciu do kilku tysięcy milisekund na ścieżce renderowania.
Domyślnie cały kod JavaScript jest blokowany przez parsowanie. Ponieważ przeglądarka nie wie, co skrypt zamierza zrobić na stronie, zakłada najgorszy scenariusz i blokuje parsowanie. Informacja dla przeglądarki, że skrypt nie musi być wykonywany w dokładnym miejscu, w którym jest wywoływany, pozwala przeglądarce kontynuować tworzenie DOM i uruchomienie skryptu, gdy będzie gotowy, np. po pobraniu pliku z pamięci podręcznej lub z dalszego serwera.
Aby to osiągnąć, do elementu <script>
dodawany jest atrybut async
:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link href="style.css" rel="stylesheet" />
<title>Critical Path: Script Async</title>
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg" /></div>
<script src="app.js" async></script>
</body>
</html>
Dodanie słowa kluczowego async do tagu skryptu informuje przeglądarkę, aby nie blokowała tworzenia DOM, dopóki skrypt nie będzie dostępny, co może znacznie poprawić wydajność.