瀏覽器的主要功能是將用戶選擇的web資源呈現出來,它需要從服務器請求資源,并將其顯示在瀏覽器窗口中,資源的格式通常是HTML,也包括PDF、image及其他格式。用戶用URI來指定所請求資源的位置,在網絡一章有更多討論。
HTML和CSS規范中規定了瀏覽器解釋html文檔的方式,由W3C組織對這些規范進行維護。
瀏覽器的主要構成(High Level Structure)
1. 用戶界面 - 包括地址欄、后退/前進按鈕、書簽目錄等,也就是你所看到的除了用來顯示你所請求頁面的主窗口之外的其他部分。
2. 瀏覽器引擎 - 用來查詢及操作渲染引擎的接口。
3. 渲染引擎 - 用來顯示請求的內容,例如,如果請求內容為html,它負責解析html及css,并將解析后的結果顯示出來。
4. 網絡 - 用來完成網絡調用,例如http請求,它具有平臺無關的接口,可以在不同平臺上工作。
5. UI后端 - 用來繪制類似組合選擇框及對話框等基本組件,具有不特定于某個平臺的通用接口,底層使用操作系統的用戶接口。
6. JS解釋器 - 用來解釋執行JS代碼。
7. 數據存儲 - 屬于持久層,瀏覽器需要在硬盤中保存類似cookie的各種數據,HTML5定義了web database技術,這是一種輕量級完整的客戶端存儲技術
渲染引擎:
渲染引擎的職責就是渲染,即在瀏覽器窗口中顯示所請求的內容。
Firefox、Chrome和Safari是基于兩種渲染引擎構建的,Firefox使用Geoko——Mozilla自主研發的渲染引擎,Safari和Chrome都使用webkit。
?渲染引擎首先通過網絡獲得所請求文檔的內容,下面是渲染引擎在取得內容之后的基本流程:
解析html以構建dom樹 -> 構建render樹 -> 布局render樹 -> 繪制render樹
Render樹構建好了之后,將會執行布局過程,它將確定每個節點在屏幕上的確切坐標。
再下一步就是繪制,即遍歷render樹,并使用UI后端層繪制每個節點。
值得注意的是,這個過程是逐步完成的,為了更好的用戶體驗,渲染引擎將會盡可能早的將內容呈現到屏幕上,并不會等到所有的html都解析完成之后再去構建和布局render樹。它是解析完一部分內容就顯示一部分內容,同時,可能還在通過網絡下載其余內容。
Webkit中元素的定位稱為布局,而Gecko中稱為回流。
hmtl不能被一般的自頂向下或自底向上的解析器所解析。
原因是:
1. 這門語言本身的寬容特性
2. 瀏覽器對一些常見的非法html有容錯機制
3. 解析過程是往復的,通常源碼不會在解析過程中發生改變,但在html中,腳本標簽包含的“document.write”可能添加標簽,這說明在解析過程中實際上修改了輸入。
瀏覽器為html定制了專屬的解析器。
瀏覽器對js腳本的解析
script標簽每次出現都會霸道的讓頁面等待腳本的解析和執行,同樣,當使用script的src屬性加載頁面時,瀏覽器必須先花時間下載外鏈文件中的代碼,然后解析并執行。在這個過程中,頁面渲染和用戶交互是完全被阻塞的。
瀏覽器之所以產生這樣的行為,是因為當前HTML頁面無從知曉JS的動作:JS可能會向document里添加內容、引入其它元素、甚至關閉標簽。
所以瀏覽器會先(下載和)執行JS代碼,然后才解析和渲染頁面。
js加載優化
想要使頁面得到更快的渲染,一是優化腳本的執行速度,例如使用觀察者模式減少初始化代碼的執行數量,這并不在這篇文章的闡述范圍之內。請牢記,默認狀態下,所有的腳本必須被執行后,頁面才會開始渲染。即在瀏覽器解析頁面之前,須先讀取并執行腳本。我們現在試著對腳本的下載過程進行優化。
現代瀏覽器都允許并行下載JS文件,但JS文件的下載過程仍然會阻塞其他比如圖片資源的下載。盡管JS文件的下載并不會相互影響,但是瀏覽器會等待全部的JS代碼下載完成才執行之。
1. 優化JS的首要規則:將腳本放在底部
這樣做可以防止腳本代碼的下載與執行阻塞頁面其它資源,比如圖片的下載(下載往往需要更多的時間),以盡量減少對整個頁面下載的影響。
如果把腳本文件放在頭部,那么需要等到所有腳本下載并執行完畢后才能下載圖片等其他資源,這是多么的可怕啊。但如果我們把腳本放在body標簽的尾部,則可以是其它資源的下載和腳本的下載與執行并發進行,能夠大大加快頁面的下載速度。
2. 減少script標簽的數量,減少延時
3. 不要把內嵌腳本緊跟在link標簽后面
這樣做會導致頁面阻塞去等待樣式表的下載,因為需要確保內嵌腳本在執行時能獲得最準確的樣式信息。
4. 減少外鏈腳本文件的數量——將多個外鏈JS文件合并成一個以減少HTTP開銷
無阻塞的腳本
無阻塞腳本意味著腳本的下載和執行不阻塞其他資源的下載和頁面的解析。
可以通過設置script標簽的defer和async屬性使其擁有不阻塞的特性,它們僅對外部鏈接的script有效。?
帶有async或者defer的script都會立刻下載并不阻塞頁面解析,它們的不同之處在于script執行的時機。
(1)defer:
1、如果有多個設置了defer的script標簽存在,則會按照順序執行所有的script;
2、defer 與 DOMContentLoaded
如果 script 標簽中包含 defer,那么這一塊腳本將不會影響 HTML 文檔的解析,而是等到 HTML 解析完成后才會執行。而 DOMContentLoaded 只有在 defer 腳本執行結束后才會被觸發。 所以這意味著什么呢?HTML 文檔解析不受影響,等 DOM 構建完成之后 defer 腳本執行,但腳本執行之前需要等待 CSSOM 構建完成。在 DOM、CSSOM 構建完畢,defer 腳本執行完成之后,DOMContentLoaded 事件觸發。
(2)async:
1、async的執行,并不會按著script在頁面中的順序來執行,而是誰先加載完誰執行。
2、async 與 DOMContentLoaded
如果 script 標簽中包含 async,則 HTML 文檔構建不受影響,解析完畢后,DOMContentLoaded 觸發,而不需要等待 async 腳本執行、樣式表加載等等。
(3)defer和async的區別:
(1)defer?和?async?在網絡讀取(下載)這塊兒是一樣的,都是異步的(相較于 HTML 解析)
(2)它倆的差別在于腳本下載完之后何時執行,顯然?defer?是最接近我們對于應用腳本加載和執行的要求的
(3)關于?defer,此圖未盡之處在于它是按照加載順序執行腳本的,這一點要善加利用
(4)async?則是一個亂序執行的主,反正對它來說腳本的加載和執行是緊緊挨著的,所以不管你聲明的順序如何,只要它加載完了就會立刻執行
仔細想想,async?對于應用腳本的用處不大,因為它完全不考慮依賴(哪怕是最低級的順序執行),不過它對于那些可以不依賴任何腳本或不被任何腳本依賴的腳本來說卻是非常合適的,最典型的例子:Google Analytics