Web Performans: Layout və Layout Thrashing

Orkhan Huseynli
Jun 26 · 7 min read

Əgər siz front-end developmentlə bir az da olsun məşğul olmusunuzsa, “DOM yavaşdır”, “DOMu bir başa manipulyasiya etmək olmaz” və hətta “Lənətə gəlsin jQueryni” kimi cümələri çox eşitmiş olarsınız. Düzdür, jQuery o qədər də günahkar olmasa da, qurunun oduna yaş da gedir. Əgər sizin yazdığınız web application yavaşdırsa, bunun üçün özünüzdən başqa heç kəsi günahlandırmayın. Ümumiyyətlə, heç bir mövzuda özünüzdən başqa heç kəsi günahlandırmayın 😉

Nə isə, mövzudan çox yayınmadan, gəlin əvvəlcə DOMun nə olduğuna və “DOM yavaşdır” cümləsinin arxasındakı mənaya diqqət yetirək.

Document Object Model (DOM) əsaslı HTML və ya XML sənədləri üçün bir interfeysdir (və ya API). O, sənədin məntiqi strukturunu təyin edir və sənədi manipulyasiya etmək imkanı yaradır. Daha sadə dildə desək, DOM, HTML sənədinin strukturunu təyin edən bir obyektir, və siz həmin obyektin strukturunu dəyişməklə, HTML sənədini də dəyişmiş olursunuz.

HTML və onun məntiqi strukturu — DOM

Bəs DOM nə vaxt yaranır? Onu JavaScript yaradır?

Əvvəla, onu qeyd etmək istərdim ki, DOM JavaScriptin bir parçası deyil, sadəcə JavaScriptə təqdim edilən bir Web APIdır. Və o, brauzer tərəfindən, səhifə yükləndiyi zaman yaradılır (bax: DOMContentLoaded).

Bəs DOMu manipulyasiya etmək nəyə görə yavaşdır? Sadəcə tree strukturlu bir obyekti dəyişmək niyə performansa bu qədər pis təsir etsin ki?

Çünki, brauzerin işi sadəcə HTML-i parse edib ağac strukturlu obyekt düzəltməkdən daha çoxdur. Bunlardan ən əsas olanlarına baxaq:

  • HTML-in parse olunması (hansı ki, o qədər də rahat iş deyil) və DOM-un qurulması

Və inanın ki, yazıq brauzerin işi bununla da bitmir. Yəqin ki, img, link, script və s. kimi xarici resursların yüklənməsini yadımızdan çıxarmamışıq :) Və nəzərə alın ki, bəzi xarici resurslar yüklənərkən, DOM-un konstruksiya prosesi dayandırılmalı olur (bax: Render-Blocking Resources). “DOM yavaşdır” cümləsini isə ona görə işlədirik ki, DOM-da hər dəyişiklik etdiyimizdə brauzer bu mərhələlərin (demək olar ki) hamısını yenidən keçməli olur.

Render Tree-nin yaradılması

Yuxarıda saydığımız mərhələlərdən ən bahalı sayılan əməliyyatlar, layoutun hazırlanması və painting prosesidir. Layoutun hazırlanması prosesi Chrome, Opera, Safari və İnternet Explorerdə layout, Firefoxda isə reflow adlandırılır.

Əgər siz layoutu dəyişirsinizsə, bu əməliyyat mütləq painting prosesini də işə salır və siz brauzerə əməlli başlı əziyyət vermiş olursunuz.

Bəs reflowa nələr səbəb olur? Əlbəttə ki, bir çox şey; Yəni siz səhifədəki, bir paraqrafın enini, uzunluğunu və s. JavaScript vasitəsilə dəyişirsinizsə, sizin bu hərəkətiniz təbii ki, layoutun yenidən hesablanmasına və yenidən rənglənməsinə səbəb olacaq. Misalçün, bir çoxumuz saytın responsivliyini yoxlamaq üçün brauzer pəncərəsini böyüdüb, kiçildib, o tərəf bu tərəfə dartışdırırıq, brauzerin nələr çəkdiyi haqqında isə heç düşünmürük.

Layout/reflowa səbəb olan əməliyyatların bütün siyahısını görmək istəyirsinizsə bu gistə baxmağınızı məsləhət görərdim.

Baxdınız? Baxmadınızsa, xahiş edirəm, baxın… Ya da, nə isə 😄

Maraqlısı odur ki, element.offsetWidth ,element.offsetHeight və s. bu kimi zərərsiz görünən komandalar da, əslində layout prosessini tətikləyirmiş. Əgər inandırıcı gəlmirsə, gəlin öz gözlərimizlə şahidi olaq.

Gəlin belə bir kod yazaq:

İnanıram ki, yazılmış kodun mahiyyəti sizə tam olaraq aydındır. Biz sadəcə bir buttona click eventi əlavə edirik və həmin button click olduğunda container klaslı bir elementin hündürlüyünü konsola yazırıq. Görüntü aşağıdakı kimi bir şey olmalıdı:

Yuxarıdakı kodun brauzerdə görüntüsü

Burda edəcəyimiz şey “Get offsetWidth” düyməsini bir neçə dəfə klikləmək, və klikləyərkən də Chrome developer toolsdakı, performans tabından nə baş verdiyini rekord etməkdir. Mən özüm bu dediklərimi etdim və performans monitorda bir az o tərəf bu tərəfə çevirib, yaxınlaşdırdıqdan sonra mənə lazım olan hissəni tapdım. Gəlin bir yerdə baxaq:

Düyməni klikləyən zaman baş verən hadisələrin detallı görünüşü

1822-ci milli saniyədən başlayaraq diqqətlə baxsaq, sarı ilə rənglənmişEvent:click zolağını görərik. Bu bizim actionButtonun klikləndiyi vaxtdır. Həmin event fire olduqdan sonra (anonymous) adlı bir funksiya çağırılmış (bu bizim event listenerə ötürdüyümüz callback funksiyasıdır), onun içərisində getElementsByClassName komandası verilmiş və daha sonra hansısa sətir tünd bənövşəyi rənglə işarələnmiş zolaqda yazıldığı kimi Recalculate Style adlı bir hadisəyə səbəb olmuşdur.

Həmin bənövşəyi qutunun üzərinə kliklədiyimizdə aşağıda summary adlı bir bölmə açılır. Və bizə style recalculationun kodun hansı sətrindən gəldiyini deyir: Recalculation Forced => (anonymous) @ (index):25 yəni 25-ci sətirdən.

Recalculate Style xülasə

Göründüyü kimi, bizim containerDiv.offsetHeight komandamız, brauzeri CSS stillərini yenidən hesablamağa vadar etmiş, və zaman oxunun qalan hissəsinə də baxsaq görərik ki, bu,Update Layer TreePaint proseslərinin tətiklənməsinə də səbəb olmuşdur.

Nəzərə alın ki, bu sadəcə çox sadə bir strukturdan ibarət olan HTML sənədidir. Və hamımız bilirik ki, gündəlik yazdığımız web saytlarda və app-lərdə daha mürəkkəb CSS qaydalarından, HTML kodlarından və daha ağır JavaScript komandalarından istifadə edirik və brauzer bunların hamsına tab gətirmək məcburiyyətindərir.

Bəs Layout Thrashing nədir?

Layout thrashing (bizim dildə desək, layoutun döyülməsi 😅) ardıcıl olaraq dəfələrlə DOM-da manipulyasiya etmək və hər dəfə ondan dəyişdirilmiş stillərin dəyərini almaq istədiyimizdə baş verir.

Daha sadə dildə desək, bayaqkı skriptə biz sadəcə elementin ölçüsünü öyrəndik, indi hesab edin ki, elementin ölçüsünü dəyişirik və dərhal onun yeni ölçüsünü öyrənmək istəyirik.

Daha aydın olsun deyə, gəlin bayaqkı kodlarda bir az dəyişiklik edib, bir daha baxaq. Deyək ki, bu dəfə düyməyə kliklədiyimizdə, container elementinin hündürlüyünü 15px artırırıq, daha sonra onun hündürlüyünü console.logedirik və bunu 100 dəfə dayanmadan təkrarlayırıq. Kod aşağıdakı kimi olacaq:

Görüntüdə isə heç bir şey dəyişməyəcək; ən azından düyməni klikləyənə qədər 😄 Gəlin, performance monitoru yenidən açaq və rekord eləməyə başlayaq. “Get offsetWidth” düyməsini bir dəfə klikləyin və rekordu dayandırın. Baxaq görək brauzerin başına nələr gəlib.

Klikdən sonra baş verən hadisələr

Müqəddəs Məryəm bizi qorusun! 😱 Uzaqdan baxdığımızda belə performans monitorun bizə göstərdiyi qırmızı rəngləri görə bilirik. Gəlin daha da yaxınlaşdıraq və görək orda nə baş verib.

Əsl layout thrashing

Yaxından baxdığmızda, click eventindən sonra baş vermiş hadisələrə diqqət yetirsək görərik ki, brauzer eyni şeyi yüz dəfə tərkar etmək məcburiyyətində qalıb:Recalculate Style , Layout , Recalculate Style , Layout

Recalculate Style zolağının (tünd bənövşəyi qısa zolaq) üzərinə kliklədiyimizdə aşağıda brauzerin bizə verdiyi xəbərdarlığı görə bilərik.

Forced reflow xəbərdarlığı (https://bit.ly/2169SYx)

Bəs bunu necə düzəldə bilərik?

İlkin olaraq tənbəlliyin və performansın qaydalarını xatırlayaq: Əgər bir şeyi etmək məcburiyyətində deyilsinizsə onu etməyin; Əgər bir şeyi etmək məcburiyyətindəsinizsə, lakin sonra edə bilərsinizsə, onu sonra edin.

Yəni, belə bir şey etmək üçün keçərli səbəbləriniz olmadığı müddətcə bunu etməyin. Əgər etmək məcburiyyətindəsinizsə, o zaman DOM-la bağlı işlərinizi sonraya saxlayın, yəni readwritelarınızı bir birindən ayırın.

Misalçün, gəlin kodda belə bir dəyişiklik edək və yenidən performans monitora baxaq.

Kodda edilmiş dəyişiklik bundan ibarətdir: DOM-dan sadəcə bir dəfə read edirik; və bizə lazım olan hündürlükləri bir arraya yığırıq. Daha sonra lazım olan bütün hündürlüklərin üzərindən keçib, hər birini container elementinə tətbiq edirik. Bəli, yazdığımız kodun, time və space mürəkkəbliyi əvvəlkindən daha çoxdur, lakin bununla biz brauzerə daha az əziyyət vermiş oluruq. Gəlin performans monitora bir daha baxaq:

Layout thrashing after fix

Sadəcə görünüşə baxsaq, bu kodla brauzeri daha az yüklədiyimiz göz qabağındadır. Gördüyünüz kimi, əvvəlki təkrarlanmalar daha yoxdur. Vaxt baxımından isə, əvvəlki kod 100 milli saniyə civarında vaxt apardığı halda, hazırki kod sadəcə 15–20 milli saniyə ərzində icra olunur. Bu ədədlər sizdə fərqli ola bilər. Ancaq əmin olun ki, ikinci versiya birincidən qat-qat sürətlidir.

React, Angular və Vue kimi front-end framework və librarylər də bizim üçün etdikləri işin bir parçası budur — batching your writes & reads to the DOM — yəni DOM-dakı oxuma və yazmalarınızı ayırmaq. Misalçün, React və Vue tərəfindən istifadə edilən edilən Virtual DOM və diff alqoritmi sırf bunun üçün nəzərdə tutulmuşdur. Təbii ki, bu framework və librarylərin işi sadəcə bundan ibarət deyil, lakin performans baxımından bu iş olduqca vacibdir.

Layout thrashingdən yaxa qurtarmağın digər yolu isə requestAnimationFramedən və ya FastDOM adlı kitabxanadan istifadə etməkdir.

Sonluq

Bütün bunları bilmək üçün xüsusi bacarığa ehtiyac yoxdur, sadəcə daimi olaraq məqlələr oxumaq və workshoplara baxmaq kifayətdir. Xüsusi olaraq, Google Developersin Web Fundamentals səhifəsində yazılmış məqalələri oxumağınızı tövsiyyə edərdim. Oxumaqla aranız yaxşı deyilsə, Steve Kinneyin Frontend Mastersdəki JavaScript Performance adlı kursuna baxın. Amma nəzərə alın ki, Steve Kinneyin danışdıqları da, elə məhz qeyd etdiyim məqalədəki mövzulardır. 😉

Məqaləni oxuduğunuz üçün təşəkkürlər!

Əgər məqalədə hər hansı bir dəyişikliyə ehtiyac olduğunu düşünürsünüzsə, xahiş edirəm mənimlə əlaqə saxlayın və ya şərhlərdə bunu qeyd edin.

Orkhan Huseynli

Written by

Front-End Engineer / JavaScript Developer