摘要:在中渲染樹中的每個節(jié)點(diǎn)即是一個渲染器或者渲染器對象。計(jì)算的樣式每個渲染器對象代表一個矩形區(qū)域通常是和一個節(jié)點(diǎn)的盒模型相對應(yīng)。坐標(biāo)系統(tǒng)是相對于根渲染器的。根渲染器的定位為和大小即為瀏覽器窗口的可視化部分比如。渲染器作廢其在屏幕上的矩形區(qū)域。
原文請查閱這里,略有刪減,本文采用知識共享署名 4.0 國際許可協(xié)議共享,BY Troland。
本系列持續(xù)更新中,Github 地址請查閱這里。
這是 JavaScript 工作原理的第十一章。
迄今為止,之前的 JavaScript 工作原理系列文章集中于關(guān)注 JavaScript 語言本身的功能,在瀏覽器中的執(zhí)行情況,如何優(yōu)化等等。
然而,當(dāng)在構(gòu)建網(wǎng)絡(luò)應(yīng)用的時候,不僅僅只是編寫自己運(yùn)行的 JavaScript 代碼。所編寫的 JavaScript 代碼與運(yùn)行環(huán)境息息相關(guān)。理解 JavaScript 運(yùn)行環(huán)境,它的運(yùn)行原理以及其組成會讓你構(gòu)建出更好的應(yīng)用并且一旦讓應(yīng)用程序運(yùn)行于各種環(huán)境下的時候,讓你更加胸有成竹地應(yīng)對潛在的問題。
那么,讓我們一探瀏覽器主要組件吧:
用戶界面: 包括地址欄,后退和前進(jìn)按鈕,書簽菜單等等。本質(zhì)上,這里包含了除顯示用戶所看到的網(wǎng)頁本身的窗口以外的瀏覽器的每個部分。
瀏覽器引擎: 處理用戶界面和渲染引擎的交互
渲染引擎: 負(fù)責(zé)顯示網(wǎng)頁。渲染引擎解析 HTML 和 CSS 并在屏幕上顯示解析的內(nèi)容。
網(wǎng)絡(luò): 使用各個平臺的不同實(shí)現(xiàn)所發(fā)起的諸如 XHR 請求的網(wǎng)絡(luò)調(diào)用,這些網(wǎng)絡(luò)調(diào)用是基于跨平臺的接口實(shí)現(xiàn)的。
UI 后端: 負(fù)責(zé)繪制諸如復(fù)選框和窗口的核心部件。它暴露出一個平臺無關(guān)的泛型接口。它底層使用操作系統(tǒng) UI 方法。
JavaScript 引擎: 我們在之前的系列文章中有詳細(xì)介紹過。基本上,這是 JavaScript 代碼執(zhí)行的地方。
數(shù)據(jù)存儲: 網(wǎng)絡(luò)應(yīng)用可能需要本地存儲所有數(shù)據(jù)。支持的存儲機(jī)制類型包括 localStorage, indexDB, WebSQL 以及 FileSystem。
本文將專注介紹渲染引擎,因?yàn)樗怯脕硖幚?HTML 和 CSS 的解析和可視化的,而這些是大多數(shù)的 JavaScript 應(yīng)用需要持續(xù)進(jìn)行交互的方面。
渲染引擎概述渲染引擎的主要職責(zé)即在瀏覽器屏幕上顯示請求的頁面。
渲染引擎可以顯示 HTML,XML 文檔以及圖片。如果使用額外的插件,就可以顯示諸如 PDF 的不同類型的文檔。
渲染引擎與 JavaScript 引擎類似,不同瀏覽器也使用不同的渲染引擎。以下為比較流行的引擎:
Gecko-Firefox
WebKit-Safari
Blink-Chrome, Opera(從版本 15 開始)
渲染過程渲染引擎從網(wǎng)絡(luò)層獲取到請求的文檔內(nèi)容。
構(gòu)建 DOM 樹渲染引擎的第一步即解析 HTML 文檔和轉(zhuǎn)化解析的元素為 DOM 樹 上的實(shí)際 DOM 節(jié)點(diǎn)。
假設(shè)有如下的文本輸入框:
Hello, friend!
HTML 的 DOM 樹類似這樣:
基本上,每個元素是直接包含于其內(nèi)的元素的父節(jié)點(diǎn)。然后依次類推。
構(gòu)建 CSSOM 樹CSSOM 即 CSS Object Model。當(dāng)瀏覽器構(gòu)建頁面的 DOM 樹的時候,它在 head 標(biāo)簽部分遇到一個引用外部 theme.css 樣式表的 link 標(biāo)簽。表示它可能需要樣式表來渲染頁面,于是便馬上分派一個請求來獲取樣式表。假設(shè)以下為 theme.css 文件內(nèi)容:
body { font-size: 16px; } p { font-weight: bold; } span { color: red; } p span { display: none; } img { float: right; }
與 HTML 一樣,渲染引擎需要把 CSS 轉(zhuǎn)化為瀏覽器可以操作的東西-即 CSSOM。以下為 CSSOM 的大概模樣:
想知道為什么 CSSOM 是樹狀結(jié)構(gòu)的嗎?當(dāng)為頁面上的任意對象計(jì)算其最終的樣式集的時候,瀏覽器先把最為通用的樣式規(guī)則應(yīng)用于該節(jié)點(diǎn)(比如,它是 body 的子節(jié)點(diǎn),會先應(yīng)用 body 的所有樣式)然后通過應(yīng)用更為具體的樣式規(guī)則來遞歸重定義計(jì)算的樣式。
讓我們看下具體的例子吧。body 中的 span 標(biāo)簽中的任何文字樣式為字體大小 16 像素且字體顏色為紅色。這些樣式繼承自 body 元素。p 元素的子元素 span 由于應(yīng)用了更為具體的樣式從而不會顯示其內(nèi)容(display:none)。
還有,請注意以上 CSSOM 樹并不完整而且只顯示了樣式表中指定的重寫樣式。每個瀏覽器提供了一份默認(rèn)的樣式集即 『用戶代理樣式』- 這即當(dāng)沒有提供任何樣式的時候的默認(rèn)顯示樣式。我們的樣式只是簡單地重寫了這些默認(rèn)樣式。
構(gòu)建渲染樹HTML 中的可視化指令和 CSSOM 樹的樣式數(shù)據(jù)結(jié)合起來創(chuàng)建渲染樹。
你可能為問渲染樹是什么?它是按順序構(gòu)建可視化元素并顯示在屏幕上的樹。它是帶有相應(yīng)的樣式的 HTML 的視覺表現(xiàn)。該樹旨在按正確的順序繪制內(nèi)容。
在 Webkit 中渲染樹中的每個節(jié)點(diǎn)即是一個渲染器或者渲染器對象。
以下為以上的 DOM 和 CSSOM 樹合成的渲染器樹的大概模樣:
為了創(chuàng)建渲染樹,瀏覽器大概做了幾下幾件事:
從 DOM 樹的根節(jié)點(diǎn)開始,遍歷每個可見節(jié)點(diǎn)。一些節(jié)點(diǎn)是不可見的(比如,script 標(biāo)簽,meta 標(biāo)簽等等),然后會被忽略,因?yàn)樗鼈儾⒉粫阡秩镜妮敵鲋酗@示。一些節(jié)點(diǎn)通過樣式隱藏然后也會被忽略。比如以上例子中的 span 節(jié)點(diǎn),因?yàn)闉槠滹@式設(shè)置了 display: none 的樣式。
瀏覽器為每個可見節(jié)點(diǎn)應(yīng)用相對應(yīng)的 CSSOM 規(guī)則并應(yīng)用這些樣式規(guī)則。
釋放出包含內(nèi)容及其經(jīng)過計(jì)算的樣式的可見節(jié)點(diǎn)。
可以瀏覽下 RenderObject 的源碼(Webkit 中):https://github.com/WebKit/web...
看一下這個類的一些核心構(gòu)件吧:
class RenderObject : public CachedImageClient { // 重繪整個對象。當(dāng)邊框顏色改變或者邊框樣式更改的時候調(diào)用。 Node* node() const { ... } RenderStyle* style; // 計(jì)算的樣式 const RenderStyle& style() const; ... }
每個渲染器對象代表一個矩形區(qū)域通常是和一個節(jié)點(diǎn)的 CSS 盒模型相對應(yīng)。它包括諸如寬度,高度以及定位的幾何信息。
渲染樹布局當(dāng)創(chuàng)建了渲染器并且添加到渲染樹的時候,它并沒有定位和大小的信息。計(jì)算這些值即稱為布局。
HTML 使用了流式布局模型,意即大多數(shù)情況下可以一次性計(jì)算出渲染器的幾何信息。坐標(biāo)系統(tǒng)是相對于根渲染器的。這里使用 Top 和 left 坐標(biāo)。
布局是一個遞歸的過程-它從根渲染器開始進(jìn)行渲染,根渲染器即 HTML 文檔的 html 元素。布局繼續(xù)通過一部分或者整個渲染器層級結(jié)構(gòu)遞歸進(jìn)行,為每個需要計(jì)算幾何信息的渲染器計(jì)算其信息。
根渲染器的定位為 0,0 和大小即為瀏覽器窗口的可視化部分(比如 viewport)。
進(jìn)行布局的過程即計(jì)算出每個節(jié)點(diǎn)在屏幕上顯示的準(zhǔn)確位置。
繪制渲染樹該階段,遍歷渲染器樹然后調(diào)用渲染器的 paint() 方法來在屏幕上顯示其內(nèi)容。
繪制可以是全局或增量式的(類似于布局):
全局-重繪整個樹。
增量-以某種方式只更改部分渲染器而不會影響到整顆樹。渲染器作廢其在屏幕上的矩形區(qū)域。這會導(dǎo)致操作系統(tǒng)把它看成是一個需要重繪的區(qū)域并生成一個 paint 事件。操作系統(tǒng)會智能地把幾個區(qū)域合并成一個以提升渲染性能。
總之,理解繪制是個漸進(jìn)式的過程是很重要的。為了更好的交互體驗(yàn),渲染引擎會試圖盡快在屏幕上顯示內(nèi)容。它不會等待所有的 HTML 結(jié)構(gòu)解析完成才開始構(gòu)建和布局渲染樹。會優(yōu)先解析和顯示部分內(nèi)容,與此同時持續(xù)處理從網(wǎng)絡(luò)接收的剩下的內(nèi)容項(xiàng)。
腳本和樣式的處理順序當(dāng)解析器遇到 標(biāo)簽的時候會立即解析和執(zhí)行該標(biāo)簽里面的代碼。整個文檔的解析會停止直到腳本執(zhí)行完畢。意即該過程是同步的。
當(dāng) script 引用的是一個外部資源,必須首先獲取該資源(也是同步的)。所有的解析會停止直到獲取該腳本資源。
HTML5 添加了一個選項(xiàng)來異步加載該資源,這樣就可以使用另外的線程來解析和執(zhí)行該資源。IE 可以使用 defer 屬性,其它可以使用 async 屬性。IE10 以下使用 defer 屬性,IE10 以上也可以使用 async 屬性。
這里有一個需要注意的地方即 IE10 以下對于 defer 的支持,打開 https://caniuse.com 查找即可發(fā)現(xiàn)對于 IE10 以下的支持是一些需要注意的地方即 defer 的腳本有可能會在 DOMContentLoaded 事件之后才開始運(yùn)行,參見這里,這里就不做試驗(yàn)了,有興趣可以點(diǎn)擊這里測試下 IE 下的表現(xiàn)。
這里稍微做一下引申,在 jQuery 源碼中,ready.js 有一段如下的代碼:
// Catch cases where $(document).ready() is called // after the browser event has already occurred. // Support: IE <=9 - 10 only // Older IE sometimes signals "interactive" too soon if ( document.readyState === "complete" || ( document.readyState !== "loading" && !document.documentElement.doScroll ) ) { // Handle it asynchronously to allow scripts the opportunity to delay ready window.setTimeout( jQuery.ready ); } else { // Use the handy event callback document.addEventListener( "DOMContentLoaded", completed ); // A fallback to window.onload, that will always work window.addEventListener( "load", completed ); }
里面的 window.setTimeout( jQuery.ready ); 是允許腳本有機(jī)會延遲執(zhí)行 ready 事件。大概是為 IE script 標(biāo)簽的 defer 屬性準(zhǔn)備的吧?
優(yōu)化渲染性能若想要優(yōu)化網(wǎng)絡(luò)應(yīng)用的性能,需要關(guān)注五個主要的方面。這些方面是你可以進(jìn)行控制的:
JavaScript-之前的文章中有介紹了編寫不阻塞 UI ,高效的代碼等等。談到渲染時候,需要考慮 JavaScript 代碼是如何和頁面上的 DOM 元素進(jìn)行交互的。JavaScript 會在界面上做很多的更改,特別是在單頁應(yīng)用中。
樣式計(jì)算-這個過程即應(yīng)用樣式規(guī)則到匹配選擇器的元素上。一旦定義了樣式規(guī)則,它們會應(yīng)用于對應(yīng)的元素,然后計(jì)算每個元素的最終樣式。
布局-一旦瀏覽器了解元素應(yīng)用的樣式規(guī)則,它會開始計(jì)算元素所占用的空間和其在瀏覽器屏幕上的顯示位置。根據(jù)網(wǎng)頁的布局模型定義一個元素的布局會影響到其它的元素。比如, 標(biāo)簽的寬度會影響到其子孫元素的寬度等等。這即意味著布局過程是相當(dāng)耗時的。繪制是在多個圖層完成的。
繪制-該階段即開始填充實(shí)際像素。這一過程包括繪制文字,顏色,圖片,邊框,陰影等所有每個元素的可見部分。
合成-因?yàn)轫撁娌糠直焕L制成潛在的多層,它們必須在屏幕以正確的順序進(jìn)行繪制,這樣頁面渲染才會正常。這是至關(guān)重要的,特別是對于那些重疊元素。
優(yōu)化 JavaScript 代碼JavaScript 經(jīng)常會在瀏覽器端觸發(fā)視覺改變。尤其是在構(gòu)建 SPA 的過程中會更多。
這里有一些優(yōu)化 JavaScript 中部分代碼來提升渲染效率的建議:
避免使用 setTimeout 或者 setInterval 來進(jìn)行視覺的更改。這些會在幀的某個時間點(diǎn)調(diào)用 callback,有可能是在幀的末尾。這樣就會造成卡頓。必須在幀的開始觸發(fā)視覺更改。
把耗時的 JavaScript 移入之前提到的網(wǎng)頁工作線程。
使用微任務(wù)來處理跨多個幀的 DOM 更改。這是為了預(yù)防當(dāng)任務(wù)需要訪問 DOM,而網(wǎng)絡(luò)工作線程無法辦到的情況的。
意即需要把一個大型的任務(wù)分割為多個小任務(wù)然后根據(jù)不同的任務(wù)性質(zhì)在 requestAnimationFrame ,setTimeout 或 setInterval 中執(zhí)行。
優(yōu)化樣式通過添加和移除元素及更改屬性等等修改 DOM 會導(dǎo)致瀏覽器重新計(jì)算元素樣式及大多數(shù)情況整個頁面或者部分頁面的布局。
使用以下方法來優(yōu)化渲染:
減少選擇器的復(fù)雜度。選擇器復(fù)雜度會占用超過計(jì)算所需元素樣式的 50% 的時間,剩余時間即構(gòu)建樣式本身。
減少必須產(chǎn)生樣式計(jì)算的元素的個數(shù)。本質(zhì)上,直接更改少數(shù)元素的樣式而不是使整個頁面的樣式失效。
優(yōu)化布局布局是很耗費(fèi)瀏覽器性能的。考慮以下優(yōu)化方案:
盡可能減少布局的數(shù)量。當(dāng)更改樣式的時候,瀏覽器檢查樣式更改是否需要重新計(jì)算布局。一般而言更改諸如 width, height, left, top 等和幾何學(xué)相關(guān)的屬性會需要布局。所以,盡可能避免修改它們。
盡可能使用 flexbox 來進(jìn)行布局而不是老式的布局模型。它會渲染得更快并且會極大地提升網(wǎng)絡(luò)應(yīng)用的性能。
避免強(qiáng)制同步布局。需要記住的是當(dāng)運(yùn)行 JavaScript的時候,上一幀的老的布局值是已知的且可以被查詢得到。當(dāng)訪問 box.offsetHeight 這并不會造成性能問題。然而,如果在訪問它之前更改它的樣式(比如為元素動態(tài)添加樣式類),瀏覽器將不得不首先應(yīng)用樣式更改然后運(yùn)行布局計(jì)算樣式。這將會非常耗時和耗資源,所以盡力避免這樣做。
優(yōu)化繪制這經(jīng)常會是所有任務(wù)中最耗時的,所以盡量避免觸發(fā)繪制。優(yōu)化方案:
更改除 transfroms 或者 opacity 外的屬性會觸發(fā)繪制。所以省著點(diǎn)用啊。
當(dāng)觸發(fā)一個布局也會觸發(fā)繪制,因?yàn)楦脑氐膸缀涡畔脑氐恼故拘Ч?/p>
通過提升層和動畫編排來減少繪制區(qū)域。
擴(kuò)展參考谷歌官方關(guān)于性能的文檔,提升元素使用如下的代碼:
.moving-element { will-change: transform; }
使用 FASTDOM 來避免強(qiáng)制同步布局和抖動。
另外關(guān)于 JavaScript 代碼的優(yōu)化方面,避免去處理一些微優(yōu)化,比如使用 offsetTop 比用 getBoundingClientRect 速度更快,但這得基于所創(chuàng)建的網(wǎng)絡(luò)應(yīng)用而言,假設(shè)創(chuàng)建一個游戲,對性能要求非常高而且調(diào)用這些方法的地方多,那么性能的提升將會很可觀的。還記得以前經(jīng)常會去使用諸如 jsperf 來測試某個方法的速度,千萬別鉆牛角尖,因地制宜,避免掉入去計(jì)較那些微小的優(yōu)化而付出過大的精力。
關(guān)于渲染可以使用一些骨架圖來提升用戶體驗(yàn)。
一些想法關(guān)于性能的體驗(yàn),其實(shí)你可以想象成造房子吧,假如是整個翻修當(dāng)然是會更加耗時,但是如果裝修某個區(qū)域就會提升性能。
然后有其中的某個屬性會提升性能,這可能理解為『工欲善其事必先利其器』。
關(guān)于任務(wù)的切分可以理解為,建設(shè)設(shè)計(jì)的哲學(xué),小技巧。
參考資源https://developers.google.com...
https://developers.google.com...
https://www.html5rocks.com/en...
本系列持續(xù)更新中,Github 地址請查閱這里。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/95511.html
摘要:在中渲染樹中的每個節(jié)點(diǎn)即是一個渲染器或者渲染器對象。計(jì)算的樣式每個渲染器對象代表一個矩形區(qū)域通常是和一個節(jié)點(diǎn)的盒模型相對應(yīng)。坐標(biāo)系統(tǒng)是相對于根渲染器的。根渲染器的定位為和大小即為瀏覽器窗口的可視化部分比如。渲染器作廢其在屏幕上的矩形區(qū)域。 原文請查閱這里,略有刪減,本文采用知識共享署名 4.0 國際許可協(xié)議共享,BY Troland。 本系列持續(xù)更新中,Github 地址請查閱這里。 ...
摘要:在中渲染樹中的每個節(jié)點(diǎn)即是一個渲染器或者渲染器對象。計(jì)算的樣式每個渲染器對象代表一個矩形區(qū)域通常是和一個節(jié)點(diǎn)的盒模型相對應(yīng)。坐標(biāo)系統(tǒng)是相對于根渲染器的。根渲染器的定位為和大小即為瀏覽器窗口的可視化部分比如。渲染器作廢其在屏幕上的矩形區(qū)域。 原文請查閱這里,略有刪減,本文采用知識共享署名 4.0 國際許可協(xié)議共享,BY Troland。 本系列持續(xù)更新中,Github 地址請查閱這里。 ...
摘要:事實(shí)是只是部分語言的不同表示法。基于這些,解析器會進(jìn)行立即或者懶解析。然而,解析器做了完全不相關(guān)的額外無用功即解析函數(shù)。這里不解析函數(shù),該函數(shù)聲明了卻沒有指出其用途。所以之前的例子,解析器實(shí)際上 原文請查閱這里,本文采用知識共享署名 4.0 國際許可協(xié)議共享,BY Troland。 本系列持續(xù)更新中,Github 地址請查閱這里。 這是 JavaScript 工作原理的第十四章。 概...
摘要:事實(shí)是只是部分語言的不同表示法。基于這些,解析器會進(jìn)行立即或者懶解析。然而,解析器做了完全不相關(guān)的額外無用功即解析函數(shù)。這里不解析函數(shù),該函數(shù)聲明了卻沒有指出其用途。所以之前的例子,解析器實(shí)際上 原文請查閱這里,本文采用知識共享署名 4.0 國際許可協(xié)議共享,BY Troland。 本系列持續(xù)更新中,Github 地址請查閱這里。 這是 JavaScript 工作原理的第十四章。 概...
閱讀 3493·2021-11-23 10:13
閱讀 863·2021-09-22 16:01
閱讀 909·2021-09-09 09:33
閱讀 630·2021-08-05 09:58
閱讀 1717·2019-08-30 11:14
閱讀 1935·2019-08-30 11:02
閱讀 3265·2019-08-29 16:28
閱讀 1478·2019-08-29 16:09