瀏覽器的主要功能是將用戶選擇的web資源呈現(xiàn)出來筋栋,它需要從服務(wù)器請求資源零远,并將其顯示在瀏覽器窗口中苗分,資源的格式通常是HTML,也包括PDF牵辣、image及其他格式摔癣。用戶用URI來指定所請求資源的位置,在網(wǎng)絡(luò)一章有更多討論纬向。
HTML和CSS規(guī)范中規(guī)定了瀏覽器解釋html文檔的方式择浊,由W3C組織對這些規(guī)范進(jìn)行維護(hù)。
瀏覽器的主要構(gòu)成(High Level Structure)
1. 用戶界面 - 包括地址欄逾条、后退/前進(jìn)按鈕琢岩、書簽?zāi)夸浀龋簿褪悄闼吹降某擞脕盹@示你所請求頁面的主窗口之外的其他部分师脂。
2. 瀏覽器引擎 - 用來查詢及操作渲染引擎的接口担孔。
3. 渲染引擎 - 用來顯示請求的內(nèi)容,例如吃警,如果請求內(nèi)容為html糕篇,它負(fù)責(zé)解析html及css,并將解析后的結(jié)果顯示出來酌心。
4. 網(wǎng)絡(luò) - 用來完成網(wǎng)絡(luò)調(diào)用娩缰,例如http請求,它具有平臺(tái)無關(guān)的接口谒府,可以在不同平臺(tái)上工作拼坎。
5. UI后端 - 用來繪制類似組合選擇框及對話框等基本組件,具有不特定于某個(gè)平臺(tái)的通用接口完疫,底層使用操作系統(tǒng)的用戶接口泰鸡。
6. JS解釋器 - 用來解釋執(zhí)行JS代碼。
7. 數(shù)據(jù)存儲(chǔ) - 屬于持久層壳鹤,瀏覽器需要在硬盤中保存類似cookie的各種數(shù)據(jù)盛龄,HTML5定義了web database技術(shù),這是一種輕量級完整的客戶端存儲(chǔ)技術(shù)
渲染引擎:
渲染引擎的職責(zé)就是渲染,即在瀏覽器窗口中顯示所請求的內(nèi)容余舶。
Firefox啊鸭、Chrome和Safari是基于兩種渲染引擎構(gòu)建的,F(xiàn)irefox使用Geoko——Mozilla自主研發(fā)的渲染引擎匿值,Safari和Chrome都使用webkit赠制。
?渲染引擎首先通過網(wǎng)絡(luò)獲得所請求文檔的內(nèi)容,下面是渲染引擎在取得內(nèi)容之后的基本流程:
解析html以構(gòu)建dom樹 -> 構(gòu)建render樹 -> 布局render樹 -> 繪制render樹
Render樹構(gòu)建好了之后挟憔,將會(huì)執(zhí)行布局過程钟些,它將確定每個(gè)節(jié)點(diǎn)在屏幕上的確切坐標(biāo)。
再下一步就是繪制绊谭,即遍歷render樹政恍,并使用UI后端層繪制每個(gè)節(jié)點(diǎn)。
值得注意的是达传,這個(gè)過程是逐步完成的篙耗,為了更好的用戶體驗(yàn),渲染引擎將會(huì)盡可能早的將內(nèi)容呈現(xiàn)到屏幕上宪赶,并不會(huì)等到所有的html都解析完成之后再去構(gòu)建和布局render樹鹤树。它是解析完一部分內(nèi)容就顯示一部分內(nèi)容,同時(shí)逊朽,可能還在通過網(wǎng)絡(luò)下載其余內(nèi)容罕伯。
Webkit中元素的定位稱為布局,而Gecko中稱為回流叽讳。
hmtl不能被一般的自頂向下或自底向上的解析器所解析追他。
原因是:
1. 這門語言本身的寬容特性
2. 瀏覽器對一些常見的非法html有容錯(cuò)機(jī)制
3. 解析過程是往復(fù)的,通常源碼不會(huì)在解析過程中發(fā)生改變岛蚤,但在html中邑狸,腳本標(biāo)簽包含的“document.write”可能添加標(biāo)簽,這說明在解析過程中實(shí)際上修改了輸入涤妒。
瀏覽器為html定制了專屬的解析器单雾。
瀏覽器對js腳本的解析
script標(biāo)簽每次出現(xiàn)都會(huì)霸道的讓頁面等待腳本的解析和執(zhí)行,同樣她紫,當(dāng)使用script的src屬性加載頁面時(shí)硅堆,瀏覽器必須先花時(shí)間下載外鏈文件中的代碼,然后解析并執(zhí)行贿讹。在這個(gè)過程中渐逃,頁面渲染和用戶交互是完全被阻塞的。
瀏覽器之所以產(chǎn)生這樣的行為民褂,是因?yàn)楫?dāng)前HTML頁面無從知曉JS的動(dòng)作:JS可能會(huì)向document里添加內(nèi)容茄菊、引入其它元素疯潭、甚至關(guān)閉標(biāo)簽。
所以瀏覽器會(huì)先(下載和)執(zhí)行JS代碼面殖,然后才解析和渲染頁面竖哩。
js加載優(yōu)化
想要使頁面得到更快的渲染,一是優(yōu)化腳本的執(zhí)行速度脊僚,例如使用觀察者模式減少初始化代碼的執(zhí)行數(shù)量相叁,這并不在這篇文章的闡述范圍之內(nèi)。請牢記吃挑,默認(rèn)狀態(tài)下钝荡,所有的腳本必須被執(zhí)行后街立,頁面才會(huì)開始渲染舶衬。即在瀏覽器解析頁面之前,須先讀取并執(zhí)行腳本赎离。我們現(xiàn)在試著對腳本的下載過程進(jìn)行優(yōu)化逛犹。
現(xiàn)代瀏覽器都允許并行下載JS文件,但JS文件的下載過程仍然會(huì)阻塞其他比如圖片資源的下載梁剔。盡管JS文件的下載并不會(huì)相互影響虽画,但是瀏覽器會(huì)等待全部的JS代碼下載完成才執(zhí)行之。
1. 優(yōu)化JS的首要規(guī)則:將腳本放在底部
這樣做可以防止腳本代碼的下載與執(zhí)行阻塞頁面其它資源荣病,比如圖片的下載(下載往往需要更多的時(shí)間)码撰,以盡量減少對整個(gè)頁面下載的影響。
如果把腳本文件放在頭部个盆,那么需要等到所有腳本下載并執(zhí)行完畢后才能下載圖片等其他資源脖岛,這是多么的可怕啊。但如果我們把腳本放在body標(biāo)簽的尾部颊亮,則可以是其它資源的下載和腳本的下載與執(zhí)行并發(fā)進(jìn)行柴梆,能夠大大加快頁面的下載速度。
2. 減少script標(biāo)簽的數(shù)量终惑,減少延時(shí)
3. 不要把內(nèi)嵌腳本緊跟在link標(biāo)簽后面
這樣做會(huì)導(dǎo)致頁面阻塞去等待樣式表的下載绍在,因?yàn)樾枰_保內(nèi)嵌腳本在執(zhí)行時(shí)能獲得最準(zhǔn)確的樣式信息。
4. 減少外鏈腳本文件的數(shù)量——將多個(gè)外鏈JS文件合并成一個(gè)以減少HTTP開銷
無阻塞的腳本
無阻塞腳本意味著腳本的下載和執(zhí)行不阻塞其他資源的下載和頁面的解析雹有。
可以通過設(shè)置script標(biāo)簽的defer和async屬性使其擁有不阻塞的特性偿渡,它們僅對外部鏈接的script有效。?
帶有async或者defer的script都會(huì)立刻下載并不阻塞頁面解析霸奕,它們的不同之處在于script執(zhí)行的時(shí)機(jī)卸察。
(1)defer:
1、如果有多個(gè)設(shè)置了defer的script標(biāo)簽存在铅祸,則會(huì)按照順序執(zhí)行所有的script坑质;
2合武、defer 與 DOMContentLoaded
如果 script 標(biāo)簽中包含 defer,那么這一塊腳本將不會(huì)影響 HTML 文檔的解析涡扼,而是等到 HTML 解析完成后才會(huì)執(zhí)行稼跳。而 DOMContentLoaded 只有在 defer 腳本執(zhí)行結(jié)束后才會(huì)被觸發(fā)。 所以這意味著什么呢吃沪?HTML 文檔解析不受影響汤善,等 DOM 構(gòu)建完成之后 defer 腳本執(zhí)行,但腳本執(zhí)行之前需要等待 CSSOM 構(gòu)建完成票彪。在 DOM红淡、CSSOM 構(gòu)建完畢,defer 腳本執(zhí)行完成之后降铸,DOMContentLoaded 事件觸發(fā)在旱。
(2)async:
1、async的執(zhí)行推掸,并不會(huì)按著script在頁面中的順序來執(zhí)行桶蝎,而是誰先加載完誰執(zhí)行。
2谅畅、async 與 DOMContentLoaded
如果 script 標(biāo)簽中包含 async登渣,則 HTML 文檔構(gòu)建不受影響,解析完畢后毡泻,DOMContentLoaded 觸發(fā)胜茧,而不需要等待 async 腳本執(zhí)行、樣式表加載等等仇味。
(3)defer和async的區(qū)別:
(1)defer?和?async?在網(wǎng)絡(luò)讀壬胪纭(下載)這塊兒是一樣的,都是異步的(相較于 HTML 解析)
(2)它倆的差別在于腳本下載完之后何時(shí)執(zhí)行邪铲,顯然?defer?是最接近我們對于應(yīng)用腳本加載和執(zhí)行的要求的
(3)關(guān)于?defer芬位,此圖未盡之處在于它是按照加載順序執(zhí)行腳本的,這一點(diǎn)要善加利用
(4)async?則是一個(gè)亂序執(zhí)行的主带到,反正對它來說腳本的加載和執(zhí)行是緊緊挨著的昧碉,所以不管你聲明的順序如何,只要它加載完了就會(huì)立刻執(zhí)行
仔細(xì)想想揽惹,async?對于應(yīng)用腳本的用處不大被饿,因?yàn)樗耆豢紤]依賴(哪怕是最低級的順序執(zhí)行),不過它對于那些可以不依賴任何腳本或不被任何腳本依賴的腳本來說卻是非常合適的搪搏,最典型的例子:Google Analytics