處理腳本和樣式表的順序
腳本
網(wǎng)絡(luò)的模型是同步的。網(wǎng)頁(yè)作者希望解析器遇到<script>標(biāo)記時(shí)立即解析并執(zhí)行腳本扳还。文檔的解析將停止,直到腳本執(zhí)行完畢橱夭。如果腳本是外部的氨距,那么解析過(guò)程會(huì)停止,直到從網(wǎng)絡(luò)同步抓取資源完成后再繼續(xù)棘劣。此模型已經(jīng)使用了多年俏让,也在HTML4 和 HTML5 規(guī)范中進(jìn)行了指定。作者也可以將腳本標(biāo)注為“defer”茬暇,這樣它就不會(huì)停止文檔解析首昔,而是等到解析結(jié)束才執(zhí)行。HTML5增加了一個(gè)選項(xiàng)糙俗,可將腳本標(biāo)記為異步勒奇,以便由其他線程解析和執(zhí)行。
預(yù)解析
WebKit和Firefox都進(jìn)行了這項(xiàng)優(yōu)化臼节。在執(zhí)行腳本時(shí)撬陵,其他線程會(huì)解析文檔的其余部分,找出并加載需要通過(guò)網(wǎng)絡(luò)加載的其他資源网缝。通過(guò)這種方式,資源可以在并行連接上加載蟋定,從而提高總體速度粉臊。請(qǐng)注意,預(yù)解析器不會(huì)修改DOM樹(shù)驶兜,而是將這項(xiàng)工作交由主解析器處理扼仲;預(yù)解析器只會(huì)解析外部資源(例如外部腳本、樣式表和圖片)的引用抄淑。
樣式表
另一方面屠凶,樣式表有著不同的模型。理論上來(lái)說(shuō)肆资,應(yīng)用樣式表不會(huì)更改 DOM 樹(shù)矗愧,因此似乎沒(méi)有必要等待樣式表并停止文檔解析。但這涉及到一個(gè)問(wèn)題郑原,就是腳本在文檔解析階段會(huì)請(qǐng)求樣式信息唉韭。如果當(dāng)時(shí)還沒(méi)有加載和解析樣式夜涕,腳本就會(huì)獲得錯(cuò)誤的回復(fù),這樣顯然會(huì)產(chǎn)生很多問(wèn)題属愤。這看上去是一個(gè)非典型案例女器,但事實(shí)上非常普遍。Firefox在樣式表加載和解析的過(guò)程中住诸,會(huì)禁止所有腳本驾胆。而對(duì)于 WebKit 而言,僅當(dāng)腳本嘗試訪問(wèn)的樣式屬性可能受尚未加載的樣式表影響時(shí)贱呐,它才會(huì)禁止該腳本俏拱。
呈現(xiàn)樹(shù)構(gòu)建
在 DOM 樹(shù)構(gòu)建的同時(shí),瀏覽器還會(huì)構(gòu)建另一個(gè)樹(shù)結(jié)構(gòu):呈現(xiàn)樹(shù)吼句。這是由可視化元素按照其顯示順序而組成的樹(shù)锅必,也是文檔的可視化表示。它的作用是讓您按照正確的順序繪制內(nèi)容惕艳。Firefox 將呈現(xiàn)樹(shù)中的元素稱為“框架”搞隐。WebKit 使用的術(shù)語(yǔ)是呈現(xiàn)器或呈現(xiàn)對(duì)象。呈現(xiàn)器知道如何布局并將自身及其子元素繪制出來(lái)远搪。
WebKits RenderObject 類是所有呈現(xiàn)器的基類劣纲,其定義如下:
每一個(gè)呈現(xiàn)器都代表了一個(gè)矩形的區(qū)域,通常對(duì)應(yīng)于相關(guān)節(jié)點(diǎn)的 CSS 框谁鳍,這一點(diǎn)在 CSS2 規(guī)范中有所描述癞季。它包含諸如寬度、高度和位置等幾何信息倘潜。
框的類型會(huì)受到與節(jié)點(diǎn)相關(guān)的“display”樣式屬性的影響绷柒。下面這段 WebKit 代碼描述了根據(jù) display 屬性的不同,針對(duì)同一個(gè) DOM 節(jié)點(diǎn)應(yīng)創(chuàng)建什么類型的呈現(xiàn)器涮因。
元素類型也是考慮因素之一废睦,例如表單控件和表格都對(duì)應(yīng)特殊的框架。在 WebKit 中养泡,如果一個(gè)元素需要?jiǎng)?chuàng)建特殊的呈現(xiàn)器嗜湃,就會(huì)替換createRenderer方法。呈現(xiàn)器所指向的樣式對(duì)象中包含了一些和幾何無(wú)關(guān)的信息澜掩。
呈現(xiàn)樹(shù)和 DOM 樹(shù)的關(guān)系
呈現(xiàn)器是和 DOM 元素相對(duì)應(yīng)的购披,但并非一一對(duì)應(yīng)。非可視化的 DOM 元素不會(huì)插入呈現(xiàn)樹(shù)中肩榕,例如“head”元素刚陡。如果元素的 display 屬性值為“none”,那么也不會(huì)顯示在呈現(xiàn)樹(shù)中(但是 visibility 屬性值為“hidden”的元素仍會(huì)顯示)。
有一些 DOM 元素對(duì)應(yīng)多個(gè)可視化對(duì)象橘荠。它們往往是具有復(fù)雜結(jié)構(gòu)的元素屿附,無(wú)法用單一的矩形來(lái)描述。例如哥童,“select”元素有 3 個(gè)呈現(xiàn)器:一個(gè)用于顯示區(qū)域挺份,一個(gè)用于下拉列表框,還有一個(gè)用于按鈕贮懈。如果由于寬度不夠匀泊,文本無(wú)法在一行中顯示而分為多行,那么新的行也會(huì)作為新的呈現(xiàn)器而添加朵你。
另一個(gè)關(guān)于多呈現(xiàn)器的例子是格式無(wú)效的 HTML各聘。根據(jù) CSS 規(guī)范,inline 元素只能包含 block 元素或 inline 元素中的一種抡医。如果出現(xiàn)了混合內(nèi)容躲因,則應(yīng)創(chuàng)建匿名的 block 呈現(xiàn)器,以包裹 inline 元素忌傻。
有一些呈現(xiàn)對(duì)象對(duì)應(yīng)于 DOM 節(jié)點(diǎn)大脉,但在樹(shù)中所在的位置與 DOM 節(jié)點(diǎn)不同。浮動(dòng)定位和絕對(duì)定位的元素就是這樣水孩,它們處于正常的流程之外镰矿,放置在樹(shù)中的其他地方,并映射到真正的框架俘种,而放在原位的是占位框架秤标。
構(gòu)建呈現(xiàn)樹(shù)的流程
在 Firefox 中,系統(tǒng)會(huì)針對(duì) DOM 更新注冊(cè)展示層宙刘,作為偵聽(tīng)器苍姜。展示層將框架創(chuàng)建工作委托給FrameConstructor,由該構(gòu)造器解析樣式(請(qǐng)參閱樣式計(jì)算)并創(chuàng)建框架荐类。
在 WebKit 中怖现,解析樣式和創(chuàng)建呈現(xiàn)器的過(guò)程稱為“附加”。每個(gè) DOM 節(jié)點(diǎn)都有一個(gè)“attach”方法玉罐。附加是同步進(jìn)行的,將節(jié)點(diǎn)插入 DOM 樹(shù)需要調(diào)用新的節(jié)點(diǎn)“attach”方法潘拨。
處理 html 和 body 標(biāo)記就會(huì)構(gòu)建呈現(xiàn)樹(shù)根節(jié)點(diǎn)吊输。這個(gè)根節(jié)點(diǎn)呈現(xiàn)對(duì)象對(duì)應(yīng)于 CSS 規(guī)范中所說(shuō)的容器 block,這是最上層的 block铁追,包含了其他所有 block季蚂。它的尺寸就是視口,即瀏覽器窗口顯示區(qū)域的尺寸。Firefox 稱之為ViewPortFrame扭屁,而 WebKit 稱之為RenderView算谈。這就是文檔所指向的呈現(xiàn)對(duì)象。呈現(xiàn)樹(shù)的其余部分以 DOM 樹(shù)節(jié)點(diǎn)插入的形式來(lái)構(gòu)建料滥。
共享樣式數(shù)據(jù)
WebKit 節(jié)點(diǎn)會(huì)引用樣式對(duì)象 (RenderStyle)然眼。這些對(duì)象在某些情況下可以由不同節(jié)點(diǎn)共享。這些節(jié)點(diǎn)是同級(jí)關(guān)系葵腹,并且:
1.這些元素必須處于相同的鼠標(biāo)狀態(tài)(例如高每,不允許其中一個(gè)是“:hover”狀態(tài),而另一個(gè)不是)
2.任何元素都沒(méi)有 ID
3.標(biāo)記名稱應(yīng)匹配
4.類屬性應(yīng)匹配
5.映射屬性的集合必須是完全相同的
6.鏈接狀態(tài)必須匹配
7.焦點(diǎn)狀態(tài)必須匹配
8.任何元素都不應(yīng)受屬性選擇器的影響践宴,這里所說(shuō)的“影響”是指在選擇器中的任何位置有任何使用了屬性選擇器的選擇器匹配
9.元素中不能有任何 inline 樣式屬性
10.不能使用任何同級(jí)選擇器鲸匿。WebCore 在遇到任何同級(jí)選擇器時(shí),只會(huì)引發(fā)一個(gè)全局開(kāi)關(guān)阻肩,并停用整個(gè)文檔的樣式共享(如果存在)带欢。這包括 + 選擇器以及 :first-child 和 :last-child 等選擇器。
布局
呈現(xiàn)器在創(chuàng)建完成并添加到呈現(xiàn)樹(shù)時(shí)烤惊,并不包含位置和大小信息乔煞。計(jì)算這些值的過(guò)程稱為布局或重排。
HTML 采用基于流的布局模型撕氧,這意味著大多數(shù)情況下只要一次遍歷就能計(jì)算出幾何信息瘤缩。處于流中靠后位置元素通常不會(huì)影響靠前位置元素的幾何特征,因此布局可以按從左至右伦泥、從上至下的順序遍歷文檔剥啤。但是也有例外情況,比如 HTML 表格的計(jì)算就需要不止一次的遍歷 (3.5)不脯。
坐標(biāo)系是相對(duì)于根框架而建立的府怯,使用的是上坐標(biāo)和左坐標(biāo)。
布局是一個(gè)遞歸的過(guò)程防楷。它從根呈現(xiàn)器(對(duì)應(yīng)于 HTML 文檔的元素)開(kāi)始牺丙,然后遞歸遍歷部分或所有的框架層次結(jié)構(gòu),為每一個(gè)需要計(jì)算的呈現(xiàn)器計(jì)算幾何信息复局。
根呈現(xiàn)器的位置左邊是 0,0冲簿,其尺寸為視口(也就是瀏覽器窗口的可見(jiàn)區(qū)域)。
所有的呈現(xiàn)器都有一個(gè)“l(fā)ayout”或者“reflow”方法亿昏,每一個(gè)呈現(xiàn)器都會(huì)調(diào)用其需要進(jìn)行布局的子代的 layout 方法峦剔。
Dirty 位系統(tǒng)
為避免對(duì)所有細(xì)小更改都進(jìn)行整體布局,瀏覽器采用了一種“dirty 位”系統(tǒng)角钩。如果某個(gè)呈現(xiàn)器發(fā)生了更改吝沫,或者將自身及其子代標(biāo)注為“dirty”呻澜,則需要進(jìn)行布局。
有兩種標(biāo)記:“dirty”和“children are dirty”惨险「遥“children are dirty”表示盡管呈現(xiàn)器自身沒(méi)有變化,但它至少有一個(gè)子代需要布局辫愉。
全局布局和增量布局
全局布局是指觸發(fā)了整個(gè)呈現(xiàn)樹(shù)范圍的布局栅受,觸發(fā)原因可能包括:
1.影響所有呈現(xiàn)器的全局樣式更改,例如字體大小更改一屋。
2.屏幕大小調(diào)整窘疮。
布局可以采用增量方式,也就是只對(duì) dirty 呈現(xiàn)器進(jìn)行布局(這樣可能存在需要進(jìn)行額外布局的弊端)冀墨。
當(dāng)呈現(xiàn)器為 dirty 時(shí)闸衫,會(huì)異步觸發(fā)增量布局。例如诽嘉,當(dāng)來(lái)自網(wǎng)絡(luò)的額外內(nèi)容添加到 DOM 樹(shù)之后蔚出,新的呈現(xiàn)器附加到了呈現(xiàn)樹(shù)中。
異步布局和同步布局
增量布局是異步執(zhí)行的虫腋。Firefox 將增量布局的“reflow 命令”加入隊(duì)列骄酗,而調(diào)度程序會(huì)觸發(fā)這些命令的批量執(zhí)行。WebKit 也有用于執(zhí)行增量布局的計(jì)時(shí)器:對(duì)呈現(xiàn)樹(shù)進(jìn)行遍歷悦冀,并對(duì) dirty 呈現(xiàn)器進(jìn)行布局趋翻。
請(qǐng)求樣式信息(例如“offsetHeight”)的腳本可同步觸發(fā)增量布局。
全局布局往往是同步觸發(fā)的盒蟆。
有時(shí)踏烙,當(dāng)初始布局完成之后,如果一些屬性(如滾動(dòng)位置)發(fā)生變化历等,布局就會(huì)作為回調(diào)而觸發(fā)讨惩。
優(yōu)化
如果布局是由“大小調(diào)整”或呈現(xiàn)器的位置(而非大小)改變而觸發(fā)的寒屯,那么可以從緩存中獲取呈現(xiàn)器的大小荐捻,而無(wú)需重新計(jì)算。
在某些情況下寡夹,只有一個(gè)子樹(shù)進(jìn)行了修改处面,因此無(wú)需從根節(jié)點(diǎn)開(kāi)始布局。這適用于在本地進(jìn)行更改而不影響周圍元素的情況菩掏,例如在文本字段中插入文本(否則每次鍵盤(pán)輸入都將觸發(fā)從根節(jié)點(diǎn)開(kāi)始的布局)鸳君。
布局處理
布局通常具有以下模式:
1.父呈現(xiàn)器確定自己的寬度。
2.父呈現(xiàn)器依次處理子呈現(xiàn)器患蹂,并且:
? ? 1.放置子呈現(xiàn)器(設(shè)置 x,y 坐標(biāo))或颊。
? ? 2.如果有必要,調(diào)用子呈現(xiàn)器的布局(如果子呈現(xiàn)器是 dirty 的传于,或者這是全局布局囱挑,或出于其他某些原因),這會(huì)計(jì)算子呈現(xiàn)器的高度沼溜。
3.父呈現(xiàn)器根據(jù)子呈現(xiàn)器的累加高度以及邊距和補(bǔ)白的高度來(lái)設(shè)置自身高度平挑,此值也可供父呈現(xiàn)器的父呈現(xiàn)器使用。
4.將其 dirty 位設(shè)置為 false系草。
Firefox 使用“state”對(duì)象 (nsHTMLReflowState) 作為布局的參數(shù)(稱為“reflow”)通熄,這其中包括了父呈現(xiàn)器的寬度。
Firefox 布局的輸出為“metrics”對(duì)象 (nsHTMLReflowMetrics)找都,其包含計(jì)算得出的呈現(xiàn)器高度
標(biāo)記時(shí)立即解析并執(zhí)行腳本唇辨。文檔的解析將停止,直到腳本執(zhí)行完畢能耻。如果腳本是外部的赏枚,那么解析過(guò)程會(huì)停止,直到從網(wǎng)絡(luò)同步抓取資源完成后再繼續(xù)晓猛。此模型已經(jīng)使用了多年饿幅,也在 HTML4 和 HTML5 規(guī)范中進(jìn)行了指定。作者也可以將腳本標(biāo)注為“defer”戒职,這樣它就不會(huì)停止文檔解析栗恩,而是等到解析結(jié)束才執(zhí)行。HTML5 增加了一個(gè)選項(xiàng)洪燥,可將腳本標(biāo)記為異步磕秤,以便由其他線程解析和執(zhí)行。