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ść, należy ustawić JavaScript asynchronicznie i wyeliminować niepotrzebny kod JavaScript z krytycznej ścieżki renderowania.
Podsumowanie
- JavaScript może wysyłać zapytania i modyfikować DOM oraz CSSOM.
- Bloki wykonywania JavaScriptu 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 wyodrębnić 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. Następnie, gdy mamy odniesienie, możemy zmienić jego tekst (za pomocą .textContent), a nawet zastąpić jego obliczoną właściwość stylu wyświetlania z „none” (brak) na „inline”. Teraz na naszej stronie wyświetli się komunikat „Cześć, interaktywni uczniowie!”.
Dzięki JavaScriptowi możemy też tworzyć, stylizować, dołączać i usuwać nowe elementy w modelu DOM. Teoretycznie cała strona może być tylko jednym dużym plikiem JavaScript, który tworzy i stylizuje elementy jeden po drugim. Choć to by zadziała, w praktyce używanie HTML i CSS jest znacznie łatwiejsze. W drugiej części funkcji JavaScript tworzymy nowy element div, ustawiamy jego zawartość tekstową, określamy jej styl i dodajemy go do treści.
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.
Po pierwsze, zwróć uwagę na to, że w poprzednim przykładzie nasz wbudowany skrypt znajduje się u dołu strony. Dlaczego? Możesz to sprawdzić samodzielnie. Jeśli przeniesiemy skrypt nad element <span>
, zauważysz, że nie działa i zgłasza błąd, ż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 w dokumencie. 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.
Inaczej mówiąc, nasz blok skryptu nie może znaleźć żadnych elementów w dalszej części strony, 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.
Inną subtelną właściwością wprowadzania skryptów na stronie jest to, że mogą one odczytywać i modyfikować nie tylko DOM, ale również właściwości CSSOM. Właśnie to robimy w tym przykładzie, gdy zmieniamy właściwość wyświetlania elementu spanu z braku 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 wykonaniem 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 i modyfikować DOM oraz CSSOM.
- Wykonywanie kodu JavaScript zostanie wstrzymane, dopóki CSSOM nie będzie gotowy.
„Optymalizacja krytycznej ścieżki renderowania” odnosi się w dużym stopniu do zrozumienia i optymalizacji grafu zależności między kodami HTML, CSS i JavaScript.
Blokowanie przez parsowanie a JavaScript asynchroniczny
Domyślnie wykonywanie JavaScriptu jest „blokowane przez parsery”. Gdy przeglądarka natrafi na skrypt w dokumencie, musi wstrzymać konstrukcję DOM, przekazać kontrolę nad środowiskiem wykonawczym JavaScriptu i pozwolić, aby skrypt został wykonany przed kontynuacją konstrukcji DOM. W poprzednim przykładzie pokazaliśmy to 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 do 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>
dodaj 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ść.