瀏覽器
在介紹瀏覽器工作流程之前,先了解一下主流瀏覽器的基礎(chǔ)結(jié)構(gòu)诗芜,本文所介紹的瀏覽器主要為開源的Chrome,F(xiàn)ireFox及部分開源的Safari,這也是目前市場占比最高的幾大瀏覽器末患,以本人博客網(wǎng)站為例,可以大致看出各瀏覽器使用比例:
瀏覽器基礎(chǔ)結(jié)構(gòu)
瀏覽器基礎(chǔ)結(jié)構(gòu)主要包括如下7部分:
1.用戶界面(User Interface):用戶所看到及與之交互的功能組件锤窑,如地址欄璧针,返回,前進按鈕等渊啰;
2.瀏覽器引擎(Browser engine):負責(zé)控制和管理下一級的渲染引擎陈莽;
3.渲染引擎(Rendering engine):負責(zé)解析用戶請求的內(nèi)容(如HTML或XML,渲染引擎會解析HTML或XML虽抄,以及相關(guān)CSS走搁,然后返回解析后的內(nèi)容);
4.網(wǎng)絡(luò)(Networking):負責(zé)處理網(wǎng)絡(luò)相關(guān)的事務(wù)迈窟,如HTTP請求等私植;
5.UI后端(UI backend):負責(zé)繪制提示框等瀏覽器組件,其底層使用的是操作系統(tǒng)的用戶接口车酣;
6.JavaScript解釋器(JavaScript interpreter):負責(zé)解析和執(zhí)行JavaScript代碼曲稼;
7.數(shù)據(jù)存儲(Data storage):負責(zé)持久存儲諸如cookie和緩存等應(yīng)用數(shù)據(jù)。
瀏覽器內(nèi)核
各大主要瀏覽器使用內(nèi)核也是有差別的湖员,大致可以分為以下幾類:
- Trident內(nèi)核: IE
- Webkit內(nèi)核:Chrome,Safari
- Gecko內(nèi)核:FireFox
網(wǎng)絡(luò)
當(dāng)用戶訪問頁面時贫悄,瀏覽器需要獲取用戶請求內(nèi)容,這個過程主要涉及瀏覽器網(wǎng)絡(luò)模塊:
1.用戶在地址欄輸入域名娘摔,如baidu.com窄坦,DNS(Domain Name System,域名解析系統(tǒng))服務(wù)器根據(jù)輸入的域名查找對應(yīng)IP凳寺,然后向該IP地址發(fā)起請求鸭津;
2.瀏覽器獲得并解析服務(wù)器的返回內(nèi)容(HTTP response);
3.瀏覽器加載HTML文件及文件內(nèi)包含的外部引用文件及圖片肠缨,多媒體等資源逆趋。
DNS預(yù)解析(DNS PREFETCH)
瀏覽器DNS解析大多時候較快,且會緩存常用域名的解析值晒奕,但是如果網(wǎng)站涉及多域名闻书,在對每一個域名訪問時都需要先解析出IP地址名斟,而我們希望在跳轉(zhuǎn)或者請求其他域名資源時盡量快,則可以開啟域名預(yù)解析魄眉,瀏覽器會在空閑時提前解析聲明需要預(yù)解析的域名砰盐,如:
多線程
我們通常說JavaScript執(zhí)行是單線程的,但是瀏覽器網(wǎng)絡(luò)部分通常是有幾個平行線程同時開啟杆融,但是也會有
限制楞卡,一般為2-6個。
渲染引擎及關(guān)鍵渲染路徑(Critical Rendering Path)
渲染引擎所做的事是將請求內(nèi)容展現(xiàn)給我們脾歇,默認支持HTML,XML和圖片類型蒋腮,對于其他諸如PDF等類型的內(nèi)容則需要安裝相應(yīng)插件,但瀏覽器的展示工作流程基本是一樣的藕各。
通過網(wǎng)絡(luò)模塊加載到HTML文件后渲染引擎渲染流程如下池摧,這也通常被稱作關(guān)鍵渲染路徑(Critical Rendering Path):
1.構(gòu)建DOM樹(DOM tree):從上到下解析HTML文檔生成DOM節(jié)點樹(DOM tree),也叫內(nèi)容樹(content tree)激况;
2.構(gòu)建CSSOM(CSS Object Model)樹:加載解析樣式生成CSSOM樹作彤;
3.執(zhí)行JavaScript:加載并執(zhí)行JavaScript代碼(包括內(nèi)聯(lián)代碼或外聯(lián)JavaScript文件);
4.構(gòu)建渲染樹(render tree):根據(jù)DOM樹和CSSOM樹,生成渲染樹(render tree)乌逐;
渲染樹:按順序展示在屏幕上的一系列矩形竭讳,這些矩形帶有字體,顏色和尺寸等視覺屬性浙踢。
5.布局(layout):根據(jù)渲染樹將節(jié)點樹的每一個節(jié)點布局在屏幕上的正確位置绢慢;
6.繪制(painting):遍歷渲染樹繪制所有節(jié)點,為每一個節(jié)點適用對應(yīng)的樣式洛波,這一過程是通過UI后端模塊完成胰舆;
為了更友好的用戶體驗,瀏覽器會盡可能快的展現(xiàn)內(nèi)容蹬挤,而不會等到文檔所有內(nèi)容到達才開始解析和構(gòu)建/布局渲染樹缚窿,而是每次處理一部分,并展現(xiàn)在屏幕上焰扳,這也是為什么我們經(jīng)尘肓悖可以看到頁面加載的時候內(nèi)容是從上到下一點一點展現(xiàn)的。
流程圖
Webkit渲染引擎流程如下圖:
Gecko渲染引擎流程如下圖:
如上圖蓝翰,Webkit瀏覽器和Gecko瀏覽器渲染流程大致相同,不同的是:
1.Webkit瀏覽器中的渲染樹(render tree)光绕,在Gecko瀏覽器中對應(yīng)的則是框架樹(frame tree),渲染對象(render object)對應(yīng)的是框架(frame);
2.Webkit中的布局(Layout)過程,在Gecko中稱為回流(Reflow),本質(zhì)是一樣的畜份,后文會解釋回流的另一層含義–重新布局;
3.Gecko中HTML和DOM樹中間多了一層內(nèi)容池(Content sink),可以理解成生成DOM元素的工廠欣尼。
單線程
不同于網(wǎng)絡(luò)部分的多線程渲染引擎是單線程工作的爆雹,意味著渲染流程是一步一步漸進完成的停蕉。
解析文檔(PARSER HTML)
在詳細介紹瀏覽器渲染文檔之前,先應(yīng)該理解瀏覽器如何解析文檔:解析文檔的順序钙态,對于CSS和JavaScript如何處理等慧起。
解析順序
瀏覽器按從上到下的順序掃描解析文檔;
解析樣式和腳本
- 腳本
或許是由于通常會在JavaScript腳本中改變文檔DOM結(jié)構(gòu)册倒,于是瀏覽器以同步方式解析蚓挤,加載和執(zhí)行腳本,瀏覽器在解析文檔時驻子,當(dāng)解析到<script>標(biāo)簽時灿意,會解析其中的腳本(對于外鏈的JavaScript文件,需要先加載該文件內(nèi)容崇呵,再進行解析)缤剧,然后立即執(zhí)行,這整個過程都會阻塞文檔解析域慷,直到腳本執(zhí)行完才會繼續(xù)解析文檔荒辕。就是說由于腳本是同步加載和執(zhí)行的,它會阻塞文檔解析犹褒,這也解釋了為什么現(xiàn)在通常建議將<script>標(biāo)簽放在</body>標(biāo)簽前面抵窒,而不是放在<head>標(biāo)簽里。現(xiàn)在HTML5提供defer和async兩個屬性支持延遲和異步加載JavaScript文件叠骑,如:
<script defer src="script.js">
-
改進
針對上文說的腳本阻塞文檔解析李皇,主流瀏覽器如Chrome和FireFox等都有一些優(yōu)化,比如在執(zhí)行腳本時座云,開啟另一個線程解析剩余的文檔以找出并加載其他的待下載外部資源(不改變主線程的DOM樹疙赠,僅優(yōu)化加載外部資源)。
-
樣式
不同于腳本朦拖,瀏覽器對樣式的處理并不會阻塞文檔解析圃阳,大概是因為樣式表并不會改變DOM結(jié)構(gòu)。
-
樣式表與腳本
你可能想問樣式是否會阻塞腳本文件的加載執(zhí)行呢璧帝?正常情況是不會的捍岳,但是存在一個問題是通常我們會在腳本中請求樣式信息,但是在文檔解析時睬隶,如果樣式尚未加載或解析锣夹,將會得到錯誤信息,對于這一問題苏潜,F(xiàn)ireFox瀏覽器和Webkit瀏覽器處理策略不同:
- 當(dāng)存在有樣式文件未被加載和解析時银萍,F(xiàn)ireFox瀏覽器會阻塞所有腳本;
- 而Webkit瀏覽器只會阻塞操作了該文件內(nèi)聲明的樣式屬性的腳本恤左。
構(gòu)建DOM樹
DOM贴唇,即文檔對象模型(Document Object Model),DOM樹搀绣,即文檔內(nèi)所有節(jié)點構(gòu)成的一個樹形結(jié)構(gòu)。
假設(shè)瀏覽器獲取返回的如下HTML文檔:
<!doctype html>
<html>
<head>
<link rel="stylesheet" href="./theme.css"></link>
<script src="./config.js"></script>
<title>關(guān)鍵渲染路徑</title>
</head>
<body>
<h1 class="title">關(guān)鍵渲染路徑</h1>
<p>關(guān)鍵渲染路徑介紹</p>
<footer>@copyright2017</footer>
</body>
</html>
首先瀏覽器從上到下依次解析文檔構(gòu)建DOM樹戳气,如下:
構(gòu)建CSSOM樹
CSSOM链患,即CSS對象模型(CSS Object Model),CSSOM樹瓶您,與DOM樹結(jié)構(gòu)相似麻捻,只是另外為每一個節(jié)點關(guān)聯(lián)了樣式信息。
theme.css樣式內(nèi)容如下:
html, body {
width: 100%;
height: 100%;
background-color: #fcfcfc;
}
.title {
font-size: 20px;
}
.footer {
font-size: 12px;
color: #aaa;
}
構(gòu)建CSSOM樹如圖:
;
執(zhí)行JAVASCRIPT
上文已經(jīng)闡述了文檔解析時對腳本的處理呀袱,我們得知腳本加載贸毕,解析和執(zhí)行會阻塞文檔解析,而在特殊情況下樣式的加載和解析也會阻塞腳本压鉴,所以現(xiàn)在推薦的實踐是<script>
標(biāo)簽放在</body>
標(biāo)簽前面崖咨。
構(gòu)建渲染樹(RENDER TREE)
DOM樹和CSSOM樹都構(gòu)建完了,接著瀏覽器會構(gòu)建渲染樹:
渲染樹油吭,代表一個文檔的視覺展示击蹲,瀏覽器通過它將文檔內(nèi)容繪制在瀏覽器窗口,展示給用戶婉宰,它由按順序展示在屏幕上的一系列矩形對象組成歌豺,這些矩形對象都帶有字體,顏色和尺寸心包,位置等視覺樣式屬性类咧。對于這些矩對象,F(xiàn)ireFox稱之為框架(frame),Webkit瀏覽器稱之為渲染對象(render object, renderer)蟹腾,后文統(tǒng)稱為渲染對象痕惋。
這里把渲染樹節(jié)點稱為矩形對象,是因為娃殖,每一個渲染對象都代表著其對應(yīng)DOM節(jié)點的CSS盒子,該盒子包含了尺寸值戳,位置等幾何信息,同時它指向一個樣式對象包含其他視覺樣式信息炉爆。
渲染樹與DOM樹
每一個渲染對象都對應(yīng)著DOM節(jié)點堕虹,但是非視覺(隱藏,不占位)DOM元素不會插入渲染樹芬首,如<head>
元素或聲明display: none;
的元素赴捞,渲染對象與DOM節(jié)點不是簡單的一對一的關(guān)系,一個DOM可以對應(yīng)一個渲染對象郁稍,但一個DOM元素也可能對應(yīng)多個渲染對象赦政,因為有很多元素不止包含一個CSS盒子,如當(dāng)文本被折行時耀怜,會產(chǎn)生多個行盒昼钻,這些行會生成多個渲染對象掸屡;又如行內(nèi)元素同時包含塊元素和行內(nèi)元素封寞,則會創(chuàng)建一個匿名塊級盒包含內(nèi)部行內(nèi)元素然评,此時一個DOM對應(yīng)多個矩形對象(渲染對象)。
渲染樹及其對應(yīng)DOM樹如圖:
- 圖中渲染樹viewport即視口狈究,是文檔的初始包含塊碗淌,scroll代表滾動區(qū)域,詳見CSS之視覺格式化模型(Visual Formatting Model)
- 渲染樹并不會包含顯式或隱式地
display:none;
的標(biāo)簽元素抖锥。
布局(LAYOUT)或回流(REFLOW亿眠,RELAYOUT)
創(chuàng)建渲染樹后,下一步就是布局(Layout),或者叫回流(reflow,relayout)磅废,這個過程就是通過渲染樹中渲染對象的信息纳像,計算出每一個渲染對象的位置和尺寸,將其安置在瀏覽器窗口的正確位置拯勉,而有些時候我們會在文檔布局完成后對DOM進行修改竟趾,這時候可能需要重新進行布局,也可稱其為回流宫峦,本質(zhì)上還是一個布局的過程岔帽,每一個渲染對象都有一個布局或者回流方法,實現(xiàn)其布局或回流导绷。
流(flow)
HTML采用的是基于流的方式定位布局犀勒,其按照從左到右,從上到下的順序進行排列妥曲,詳見CSS定位機制贾费。
全局布局與局部布局
對渲染樹的布局可以分為全局和局部的,全局即對整個渲染樹進行重新布局檐盟,如當(dāng)我們改變了窗口尺寸或方向或者是修改了根元素的尺寸或者字體大小等褂萧;而局部布局可以是對渲染樹的某部分或某一個渲染對象進行重新布局。
臟位系統(tǒng)(dirty bit system)
大多數(shù)web應(yīng)用對DOM的操作都是比較頻繁遵堵,這意味著經(jīng)常需要對DOM進行布局和回流箱玷,而如果僅僅是一些小改變,就觸發(fā)整個渲染樹的回流陌宿,這顯然是不好的锡足,為了避免這種情況,瀏覽器使用了臟位系統(tǒng)壳坪,只有一個渲染對象改變了或者某渲染對象及其子渲染對象臟位值為”dirty”時舶得,說明需要回流。
表示需要布局的臟位值有兩種:
- “dirty”–自身改變爽蝴,需要回流
- “children are dirty”–子節(jié)點改變沐批,需要回流
布局過程
布局是一個從上到下纫骑,從外到內(nèi)進行的遞歸過程,從根渲染對象九孩,即對應(yīng)著HTML文檔根元素<html>
先馆,然后下一級渲染對象,如對應(yīng)著<body>
元素躺彬,如此層層遞歸煤墙,依次計算每一個渲染對象的幾何信息(位置和尺寸)。
幾何信息-位置和尺寸宪拥,即相對于窗口的坐標(biāo)和尺寸仿野,如根渲染對象,其坐標(biāo)為(0她君, 0)脚作,尺寸即是視口
尺寸(瀏覽器窗口的可視區(qū)域)。
每一個渲染對象的布局流程基本如:
- 1.計算此渲染對象的寬度(width)缔刹;
- 2.遍歷此渲染對象的所有子級球涛,依次:
- 2.1設(shè)置子級渲染對象的坐標(biāo)
- 2.2判斷是否需要觸發(fā)子渲染對象的布局或回流方法,計算子渲染對象的高度(height)
- 3.設(shè)置此渲染對象的高度:根據(jù)子渲染對象的累積高桨螺,margin和padding的高度設(shè)置其高度宾符;
- 4.設(shè)置此渲染對象臟位值為false。
強制回流
在渲染樹布局完成后灭翔,再次操作文檔魏烫,改變文檔的內(nèi)容或結(jié)構(gòu),或者元素定位時肝箱,會觸發(fā)回流哄褒,即需要重新布局,如請求某DOM的”offsetHeight”樣式信息等諸多情況:
- DOM操作煌张,如增加呐赡,刪除,修改或移動骏融;
- 變更內(nèi)容链嘀;
- 激活偽類;
- 訪問或改變某些CSS屬性(包括修改樣式表或元素類名或使用JavaScript操作等方式)档玻;
- 瀏覽器窗口變化(滾動或尺寸變化)
$('body').css('padding'); // reflow
$('body')[0].offsetHeight; // relow
有過CSS3動畫開發(fā)經(jīng)驗的同學(xué)可能會有經(jīng)歷怀泊,如下入場動畫:
.slide-left {
-webkit-transition: margin-left 1s ease-out;
-moz-transition: margin-left 1s ease-out;
-o-transition: margin-left 1s ease-out;
transition: margin-left 1s ease-out;
}
然后執(zhí)行如下腳本:
var $slide = $('.slide-left');
$slide.css({
"margin-left": "100px"
}).addClass('slide-left');
$slide.css({
"margin-left": "10px"
});
我們會發(fā)現(xiàn)并沒有效果,為什么呢误趴?因為對margin-left的修改并沒有觸發(fā)回流霹琼,元素margin-left值的改變被緩存,如果我們在中間強制觸發(fā)回流:
var $slide = $('.slide-left');
$slide.css({
"margin-left": "100px"
});
console.log($slide.css('padding'));
$slide.addClass('slide-left');
$slide.css({
"margin-left": "10px"
});
再看就達到了預(yù)期效果。
繪制(PAINTING)
最后是繪制(paint)階段或重繪(repaint)階段枣申,瀏覽器UI組件將遍歷渲染樹并調(diào)用渲染對象的繪制(paint)方法售葡,將內(nèi)容展現(xiàn)在屏幕上,也有可能在之后對DOM進行修改忠藤,需要重新繪制渲染對象挟伙,也就是重繪,繪制和重繪的關(guān)系可以參考布局和回流的關(guān)系熄驼。
全局與局部繪制
與布局相似像寒,繪制也分為全局和局部繪制,即對整個渲染樹或某些渲染對象進行繪制瓜贾。
觸發(fā)重繪
我們已經(jīng)知道很多操作可能會觸發(fā)回流,那么什么時候可能觸發(fā)重繪呢携悯,通常祭芦,當(dāng)改變元素的視覺樣式,如background-color,visibility憔鬼,margin龟劲,padding或字體顏色時會觸發(fā)全局或局部重繪,如:
$('body').css('color', 'red'); // repaint
$('body').css('margin', '2px'); // reflow, repaint
頁面渲染優(yōu)化
瀏覽器對上文介紹的關(guān)鍵渲染路徑進行了很多優(yōu)化轴或,針對每一次變化產(chǎn)生盡量少的操作昌跌,還有優(yōu)化判斷重新繪制或布局的方式等等。
在改變文檔根元素的字體顏色等視覺性信息時照雁,會觸發(fā)整個文檔的重繪蚕愤,而改變某元素的字體顏色則只觸發(fā)特定元素的重繪;改變元素的位置信息會同時觸發(fā)此元素(可能還包括其兄弟元素或子級元素)的布局和重繪饺蚊。某些重大改變萍诱,如更改文檔根元素<html>
的字體尺寸,則會觸發(fā)整個文檔的重新布局和重繪污呼,據(jù)此及上文所述裕坊,推薦以下優(yōu)化和實踐:
- 1.HTML文檔結(jié)構(gòu)層次盡量少,最好不深于六層燕酷;
- 2.腳本盡量后放籍凝,放在
</body>
前即可; - 3.少量首屏樣式內(nèi)聯(lián)放在
<head>
標(biāo)簽內(nèi)苗缩; - 4.樣式結(jié)構(gòu)層次盡量簡單饵蒂;
- 5.在腳本中盡量減少DOM操作,盡量緩存訪問DOM的樣式信息挤渐,避免過度觸發(fā)回流苹享;
- 6.減少通過JavaScript代碼修改元素樣式,盡量使用修改class名方式操作樣式或動畫;
- 7.動畫盡量使用在絕對定位或固定定位的元素上得问;
- 8.隱藏在屏幕外囤攀,或在頁面滾動時,盡量停止動畫宫纬;
- 9.盡量緩存DOM查找焚挠,查找器盡量簡潔;
- 10.涉及多域名的網(wǎng)站漓骚,可以開啟域名預(yù)解析
實例
當(dāng)我們訪問一個頁面時蝌衔,瀏覽器渲染事件詳細日志圖如下:
- 1.發(fā)起請求;
- 2.解析HTML蝌蹂;
- 3.解析樣式噩斟;
- 4.執(zhí)行JavaScript;
- 5.布局孤个;
- 6.繪制
轉(zhuǎn)自:http://blog.codingplayboy.com/2017/03/29/webpage_render/