今天上午寫display:none、opacity:0、visibility:hidden的時候拓展了一些關于浏覽器加載,解析頁面的東西,是以晚上寫一篇關于浏覽器是如何加載,解析,渲染的過程。其實以前也在各種論壇上看過很多大神寫的關于浏覽器的加載,解析,渲染的這一系列文章,但是說實話,诶呀,根本就沒法統一嘛,真的是看的腦袋瓜子都大了,每個人說的都不一樣...說實話給我造成了蠻大的困惑的,直到後來是看到了一篇經典的文章 howbrowserswork 對英語足夠自信的朋友可以點進去看看,要是讀英語覺着煩的話,也可以看看中文版的,我覺得這篇文章寫得蠻好的,我自己了解的依據也大都源自于這篇文章。
幾個重要的基本概念
在此之前我們需要了解一些相關的基本概念:
- DOM(Document Object Model):浏覽器将html解析為樹型資料結構,簡稱DOM,DOM和标簽基本是一一對應的關系。
- CSSOM( CSS Object Model):浏覽器将css解析為樹型資料結構,簡稱CSSOM。
- Redner Tree: 浏覽器根據dom+cssom合并之後生成render tree。如下圖:
- Layout:浏覽器計算Render Tree中每個節點的具體位置。
- Painting:通過顯示卡,将經過layout 之後的節點顯示在浏覽器中。
- Reflow(回流):浏覽器發現某個元素發生變化影響了布局,就會需要倒回去重新渲染。(比如執行某句js,改行js改變了某個元素的寬度,高度,那浏覽器就會重新回去,對那部分元素重新渲染)
浏覽器解析主體流程
上面是幾個非常重要的概念,下面我們先直接給出浏覽器解析的大概流程。
解析html以建構dom樹 -> 建構render樹 -> 布局render樹 -> 繪制render樹
和這個流程搭配的還有一個圖,如下:
由上述流程圖我們可以發現:
- HTML/SVG/XHTML等會解析出一個DOM樹。
- 解析CSS會生成一個對應的css規則樹。
- js的腳本主要是通過DOM API 和CSSOM API來分别操作dom tree 和css rule tree
那麼接下來我們看看浏覽器的整個執行過程大約是怎樣的:
當浏覽器獲得一個html檔案時,會
“自上而下”
加載,并在加載過程中進行解析渲染。
解析:
1. 浏覽器會将HTML解析成一個DOM樹,DOM 樹的建構過程是一個深度周遊過程:目前節點的所有子節點都建構好後才會去建構目前節點的下一個兄弟節點。
2. 将CSS解析成 CSS Rule Tree 。
3. 根據DOM樹和CSSOM來構造 Rendering Tree。注意:Rendering Tree 渲染樹并不等同于 DOM 樹,因為一些像 Header 或 display:none 的東西就沒必要放在渲染樹中了。
4.有了Render Tree,浏覽器已經能知道網頁中有哪些節點、各個節點的CSS定義以及他們的從屬關系。下一步操作稱之為Layout,顧名思義就是計算出每個節點在螢幕中的位置。
5.再下一步就是繪制,即周遊render樹,并使用UI後端層繪制每個節點。
上述這個過程是
逐漸完成
的,為了更好的使用者體驗,渲染引擎将會盡可能早的将内容呈現到螢幕上,并不會等到所有的html都解析完成之後再去建構和布局render樹。它是解析完一部分内容就顯示一部分内容,同時,可能還在通過網絡下載下傳其餘内容。
上面這段内容是我看了很多大神的文章,我覺得寫得最能讓人信服的一個,也是寫得比較清楚的一個。
腳本js
web的模式是同步的,開發者希望解析到一個script标簽時立即解析執行腳本,并阻塞文檔的解析直到腳本執行完。如果腳本是外引的,則網絡必須先請求到這個資源——這個過程也是同步的,會阻塞文檔的解析直到資源被請求到。這個模式保持了很多年,并且在html4及html5中都特别指定了。開發者可以将腳本辨別為defer,以使其不阻塞文檔解析,并在文檔解析結束後執行。Html5增加了标記腳本為異步的選項,以使腳本的解析執行使用另一個線程。
預解析
Webkit和Firefox都做了這個優化,當執行腳本時,另一個線程解析剩下的文檔,并加載後面需要通過網絡加載的資源。這種方式可以使資源并行加載進而使整體速度更快。需要注意的是,預解析并不改變Dom樹,它将這個工作留給主解析過程,自己隻解析外部資源的引用,比如外部腳本、樣式表及圖檔。
樣式表
樣式表采用另一種不同的模式。理論上,既然樣式表不改變Dom樹,也就沒有必要停下文檔的解析等待它們,然而,存在一個問題,腳本可能在文檔的解析過程中請求樣式資訊,如果樣式還沒有加載和解析,腳本将得到錯誤的值,顯然這将會導緻很多問題,這看起來是個邊緣情況,但确實很常見。Firefox在存在樣式表還在加載和解析時阻塞所有的腳本,而Chrome隻在當腳本試圖通路某些可能被未加載的樣式表所影響的特定的樣式屬性時才阻塞這些腳本。
渲染樹建構
當Dom樹建構完成時,浏覽器開始建構另一棵樹——渲染樹。渲染樹由元素顯示序列中的可見元素組成,它是文檔的可視化表示,建構這棵樹是為了以正确的順序繪制文檔内容。
Firefox将渲染樹中的元素稱為frames,WebKit則用renderer或渲染對象來描述這些元素。
一個渲染對象知道怎麼布局及繪制自己及它的children。
每個渲染對象用一個和該節點的css盒模型相對應的矩形區域來表示,正如css2所描述的那樣,它包含諸如寬、高和位置之類的幾何資訊。盒模型的類型受該節點相關的display樣式屬性的影響
渲染樹和DOM樹之間的關系
渲染對象和Dom元素相對應,但這種對應關系不是一對一的,不可見的Dom元素不會被插入渲染樹,例如head元素。另外,display屬性為none的元素也不會在渲染樹中出現(visibility屬性為hidden的元素将出現在渲染樹中)。
還有一些Dom元素對應幾個可見對象,它們一般是一些具有複雜結構的元素,無法用一個矩形來描述。例如,select元素有三個渲染對象——一個顯示區域、一個下拉清單及一個按鈕。同樣,當文本因為寬度不夠而折行時,新行将作為額外的渲染元素被添加。另一個多個渲染對象的例子是不規範的html,根據css規範,一個行内元素隻能僅包含行内元素或僅包含塊狀元素,在存在混合内容時,将會建立匿名的塊狀渲染對象包裹住行内元素。
一些渲染對象和所對應的Dom節點不在樹上相同的位置,例如,浮動和絕對定位的元素在文本流之外,在兩棵樹上的位置不同,渲染樹上辨別出真實的結構,并用一個占位結構辨別出它們原來的位置。
性能的優化
上面也說道了如果js在解析的時候發現某個元素的寬高發生變化會發生reflow,即浏覽器需要回過頭去重新渲染那部分區域。
那我們在知道了浏覽器的渲染一個頁面整體流程之後就可以在寫代碼時候盡量減少浏覽器發生reflow的情況。
比如一張圖檔,我們沒有設定寬高,那等浏覽器請求到圖檔之後,發現之前img部分的尺寸肯定發生變化了,那就需要回頭reflow其實避免這種情況很簡單,隻需要實作設定一下寬高(根據業務需求)那浏覽器在第一次解析img這個标簽的時候就知道要給他預留出多大的尺寸了,那等圖檔請求回來的時候就不會影響布局了。最多隻是發生重繪,而重繪的性能消耗是要遠小于重排的。
再比如我們通過js修改某個元素的樣式的時候,比如通過js先修改了一個元素的高,然後在修改這個元素的寬,然後再修改這個元素的背景色,那其實我們可以把這三個需要修改的樣式放在一個class中,将這個元素的class改變,樣式自然就能達到我們想要的效果了,這樣寫不僅代碼簡介優雅,而且發生reflow的次數會少很多。這也是能提升浏覽器性能需要注意的一個點。
好了,關于浏覽器渲染機制就寫到這兒,如果各位大神有什麼不同的簡介,非常歡迎一起交流。如有錯誤,也歡迎指正。