一. 瀏覽器簡介
1. 瀏覽器種類
- 目前使用的主流瀏覽器有五個:Internet Explorer族淮、Firefox袱衷、Safari聪廉、Chrome 瀏覽器和 Opera。本文中以開放源代碼瀏覽器為例,即 Firefox类浪、Chrome 瀏覽器和 Safari(部分開源)。
2. 瀏覽器結(jié)構(gòu)
- 用戶界面 : 包括地址欄、前進/后退按鈕心肪、書簽菜單等。除了瀏覽器主窗口顯示的您請求的頁面外纠吴,其他顯示的各個部分都屬于用戶界面硬鞍。
- 瀏覽器引擎 : 在用戶界面和呈現(xiàn)引擎之間傳送指令。
- 呈現(xiàn)引擎 : 負責顯示請求的內(nèi)容呜象。如果請求的內(nèi)容是 HTML膳凝,它就負責解析 HTML 和 CSS 內(nèi)容,并將解析后的內(nèi)容顯示在屏幕上恭陡。
- 網(wǎng)絡 : 用于網(wǎng)絡調(diào)用蹬音,比如 HTTP 請求。其接口與平臺無關(guān)休玩,并為所有平臺提供底層實現(xiàn)著淆。
- 用戶界面后端 : 用于繪制基本的窗口小部件,比如組合框和窗口拴疤。其公開了與平臺無關(guān)的通用接口永部,而在底層使用操作系統(tǒng)的用戶界面方法。
- JavaScript 解釋器 : 用于解析和執(zhí)行 JavaScript 代碼呐矾。
- 數(shù)據(jù)存儲 : 這是持久層苔埋。瀏覽器需要在硬盤上保存各種數(shù)據(jù),例如 Cookie蜒犯。新的 HTML 規(guī)范 (HTML5) 定義了“網(wǎng)絡數(shù)據(jù)庫”组橄,這是一個完整(但是輕便)的瀏覽器內(nèi)數(shù)據(jù)庫荞膘。
-
瀏覽器結(jié)構(gòu)圖
3. 渲染流程簡介
- 呈現(xiàn)引擎將開始解析 HTML 文檔,并將各標記逐個轉(zhuǎn)化成“內(nèi)容樹”上的 DOM 節(jié)點玉工。同時也會解析外部 CSS 文件以及樣式元素中的樣式數(shù)據(jù)羽资。HTML 中這些帶有視覺指令的樣式信息將用于創(chuàng)建另一個樹結(jié)構(gòu):呈現(xiàn)樹.
- 呈現(xiàn)樹包含多個帶有視覺屬性(如顏色和尺寸)的矩形。這些矩形的排列順序就是它們將在屏幕上顯示的順序遵班。
- 呈現(xiàn)樹構(gòu)建完畢之后屠升,進入“布局”處理階段,也就是為每個節(jié)點分配一個應出現(xiàn)在屏幕上的確切坐標狭郑。下一個階段是繪制 - 呈現(xiàn)引擎會遍歷呈現(xiàn)樹腹暖,由用戶界面后端層將每個節(jié)點繪制出來。
-
渲染主流程圖
二. 瀏覽器渲染流程詳解
1. 解析
什么是解析:解析是呈現(xiàn)引擎中非常重要的一個環(huán)節(jié)愿阐,解析文檔是指將文檔轉(zhuǎn)化成為有意義的結(jié)構(gòu)微服,也就是可讓代碼理解和使用的結(jié)構(gòu)。解析得到的結(jié)果通常是代表了文檔結(jié)構(gòu)的節(jié)點樹缨历,它稱作解析樹或者語法樹以蕴。
-
解析的過程:詞法分析和語法分析
- 詞法分析: 將輸入內(nèi)容分割成大量標記的過程。標記是語言中的詞匯辛孵,即構(gòu)成內(nèi)容的單位丛肮。在人類語言中,它相當于語言字典中的單詞魄缚。
- 語法分析: 是應用語言的語法規(guī)則的過程宝与。
-
解析示例
-
文檔:
2 + 3 - 1
-
詞法定義:詞匯通常用==正則表達式==表示
詞法:我們用的語言可包含整數(shù)、加號和減號
INTEGER :0|[1-9][0-9]* PLUS : + MINUS: -
-
語法定義:語法通常使用一種稱為==BNF==的格式來定義
- 構(gòu)成語言的語法單位是表達式冶匹、項和運算符习劫。
- 我們用的語言可以包含任意數(shù)量的表達式。
- 表達式的定義是:一個“項”接一個“運算符”嚼隘,然后再接一個“項”诽里。
- 運算符是加號或減號。
- 項是一個整數(shù)或一個表達式
expression := term operation term operation := PLUS | MINUS term := INTEGER | expression
-
解析樹:
- 讓我們分析一下
2 + 3 - 1
, 匹配語法規(guī)則的第一個子串是 2飞蛹,而根據(jù)第 5 條語法規(guī)則谤狡,這是一個項。匹配語法規(guī)則的第二個子串是 2 + 3卧檐,而根據(jù)第 3 條規(guī)則(一個項接一個運算符墓懂,然后再接一個項),這是一個表達式霉囚。下一個匹配項已經(jīng)到了輸入的結(jié)束捕仔。2 + 3 - 1 是一個表達式,因為我們已經(jīng)知道 2 + 3 是一個項,這樣就符合“一個項接一個運算符榜跌,然后再接一個項”的規(guī)則闸天。2 + + 不與任何規(guī)則匹配,因此是無效的輸入斜做。
- 讓我們分析一下
-
2. DOM樹構(gòu)建
-
文檔:
<!DOCTYPE html> <html> <body> <div> <h1 class="title">demo</h1> <input value="hello"> </div> </body> </html>
詞法分析: 瀏覽器中的詞法分析器負責將輸入內(nèi)容分解成一個個有效標記.
在chrome中有個
HTMLDocumentParser
的c++類就負責解析html文本為tokens,一個token就是一個標簽文本的序列化,并借助HTMLTreeBuilder
對這些tokens分類處理湾揽,根據(jù)不同的標簽類型瓤逼、在文檔不同位置,調(diào)用HTMLConstructionSite
不同的函數(shù)構(gòu)建DOM樹库物。-
這里我們只要關(guān)注序列化后的token是什么東西就好了霸旗,為此,寫了一個函數(shù)戚揭,把tokens的一些關(guān)鍵信息打印出來
String getTokenInfo(){ String tokenInfo = ""; tokenInfo = "tagName: " + this->m_name + "|type: " + getType() + "|attr:" + getAttributes() + "|text: " + this->m_data; return tokenInfo; }
tagName: html |type: DOCTYPE |attr: |text: " tagName: |type: Character |attr: |text: \n" tagName: html |type: startTag |attr: |text: " tagName: |type: Character |attr: |text: \n" tagName: body |type: startTag |attr: |text: " tagName: |type: Character |attr: |text: \n " tagName: div |type: startTag |attr: |text: " tagName: |type: Character |attr: |text: \n " tagName: h1 |type: startTag |attr:class=title |text: " tagName: |type: Character |attr: |text: demo" tagName: h1 |type: EndTag |attr: |text: " tagName: |type: Character |attr: |text: \n " tagName: input |type: startTag |attr:value=hello |text: " tagName: |type: Character |attr: |text: \n " tagName: div |type: EndTag |attr: |text: " tagName: |type: Character |attr: |text: \n" tagName: body |type: EndTag |attr: |text: " tagName: |type: Character |attr: |text: \n" tagName: html |type: EndTag |attr: |text: " tagName: |type: Character |attr: |text: \n" tagName: |type: EndOfFile |attr: |text: "
- 語法分析:瀏覽器中的解析器負責根據(jù)語言的語法規(guī)則分析文檔的結(jié)構(gòu)诱告,從而構(gòu)建解析樹, HTML 的定義采用了 ==DTD== 格式。此格式可用于定義 SGML 族的語言民晒。它包括所有允許使用的元素及其屬性和層次結(jié)構(gòu)的定義
- 樹構(gòu)建算法
樹構(gòu)建階段的輸入是一個來自標記化階段的<html> <body> Hello world </body> </html>
標記序列Tokens
精居。- 第一個模式是
initial mode
。接收HTML
標記后轉(zhuǎn)為before html
模式潜必,并在這個模式下重新處理此標記靴姿。這樣會創(chuàng)建一個HTMLHtmlElement
元素,并將其附加到Document
根對象上磁滚。 - 然后狀態(tài)將改為
before head
佛吓。此時我們接收body
標記。即使我們的示例中沒有head
標記垂攘,系統(tǒng)也會隱式創(chuàng)建一個HTMLHeadElement
维雇,并將其添加到樹中。 - 現(xiàn)在我們進入了
in head
模式晒他,然后轉(zhuǎn)入after head
模式吱型。系統(tǒng)對body
標記進行重新處理,創(chuàng)建并插入HTMLBodyElement
仪芒,同時模式轉(zhuǎn)變?yōu)?code>in body唁影。 - 現(xiàn)在,接收由
Hello world
字符串生成的一系列字符標記掂名。接收第一個字符時會創(chuàng)建并插入Text
節(jié)點据沈,而其他字符也將附加到該節(jié)點。 - 接收
body
結(jié)束標記會觸發(fā)after body
模式〗让铮現(xiàn)在我們將接收HTML
結(jié)束標記锌介,然后進入after after body
模式。接收到文件結(jié)束標記后,解析過程就此結(jié)束孔祸。
- 第一個模式是
-
DOM解析樹
3. CSSOM樹構(gòu)建
- 文檔:
p, div { margin-top: 3px; } .error { color: red; }
- 詞法:
comment \/\*[^*]*\*+([^/*][^*]*\*+)*\/ num [0-9]+|[0-9]*"."[0-9]+ nonascii [\200-\377] nmstart [_a-z]|{nonascii}|{escape} nmchar [_a-z0-9-]|{nonascii}|{escape} name {nmchar}+ ident {nmstart}{nmchar}*
- 語法:
ruleset : selector [ ',' S* selector ]* '{' S* declaration [ ';' S* declaration ]* '}' S* ; selector : simple_selector [ combinator selector | S+ [ combinator? selector ]? ]? ; simple_selector : element_name [ HASH | class | attrib | pseudo ]* | [ HASH | class | attrib | pseudo ]+ ; class : '.' IDENT ; element_name : IDENT | '*' ; attrib : '[' S* IDENT S* [ [ '=' | INCLUDES | DASHMATCH ] S* [ IDENT | STRING ] S* ] ']' ; pseudo : ':' [ IDENT | FUNCTION S* [IDENT S*] ')' ] ;
-
CSSOM解析樹
4. 呈現(xiàn)樹構(gòu)建
在 DOM 樹構(gòu)建的同時隆敢,瀏覽器還會構(gòu)建另一個樹結(jié)構(gòu):呈現(xiàn)樹。這是由可視化元素按照其顯示順序而組成的樹崔慧,也是文檔的可視化表示拂蝎。它的作用是讓您按照正確的順序繪制內(nèi)容。
Firefox 將呈現(xiàn)樹中的元素稱為“框架”. WebKit 使用的術(shù)語是呈現(xiàn)器或呈現(xiàn)對象惶室。
呈現(xiàn)器知道如何布局并將自身及其子元素繪制出來温自。
WebKits RenderObject 類是所有呈現(xiàn)器的基類,其定義如下:
class RenderObject{
virtual void layout();
virtual void paint(PaintInfo);
virtual void rect repaintRect();
Node* node; //the DOM node
RenderStyle* style; // the computed style
RenderLayer* containgLayer; //the containing z-index layer
}
每一個呈現(xiàn)器都代表了一個矩形的區(qū)域皇钞,通常對應于相關(guān)節(jié)點的 CSS 框悼泌,這一點在 CSS2 規(guī)范中有所描述。它包含諸如寬度夹界、高度和位置等幾何信息馆里。
框的類型會受到與節(jié)點相關(guān)的“display”樣式屬性的影響(請參閱樣式計算章節(jié))。下面這段 WebKit 代碼描述了根據(jù) display 屬性的不同可柿,針對同一個 DOM 節(jié)點應創(chuàng)建什么類型的呈現(xiàn)器鸠踪。
- 呈現(xiàn)樹和DOM樹對照關(guān)系:
-
呈現(xiàn)樹構(gòu)建示例:
- HTML文檔:
<html> <body> <div class="err" id="div1"> <p> this is a <span class="big"> big error </span> this is also a <span class="big"> very big error</span> error </p> </div> <div class="err" id="div2">another error</div> </body> </html>
- CSS樣式:
1. div { margin:5px;color:black } 2. .err { color:red } 3. .big { margin-top:3px } 4. div span { margin-bottom:4px } 5. #div1 { color:blue } 6. #div2 { color:green }
- 顯現(xiàn)樹樣式計算:使用規(guī)則樹計算樣式上下文樹
- 樣式上下文樹: Firefox 還采用了樣式上下文樹, WebKit 也有樣式對象,但它們不是保存在類似樣式上下文樹這樣的樹結(jié)構(gòu)中复斥,只是由 DOM 節(jié)點指向此類對象的相關(guān)樣式慢哈。
-
規(guī)則樹: 所有匹配的規(guī)則都存儲在樹中。路徑中的底層節(jié)點擁有較高的優(yōu)先級永票。規(guī)則樹包含了所有已知規(guī)則匹配的路徑卵贱。規(guī)則的存儲是延遲進行的。規(guī)則樹不會在開始的時候就為所有的節(jié)點進行計算侣集,而是只有當某個節(jié)點樣式需要進行計算時键俱,才會向規(guī)則樹添加計算的路徑。
樣式表解析完畢后世分,系統(tǒng)會根據(jù)選擇器將 CSS規(guī)則添加到某個哈希表中编振。這些哈希表的選擇器各不相同,包括ID臭埋、類名稱踪央、標記名稱等,還有一種通用哈希表瓢阴,適合不屬于上述類別的規(guī)則畅蹂。如果選擇器是 ID,規(guī)則就會添加到 ID 表中荣恐;如果選擇器是類液斜,規(guī)則就會添加到類表中累贤,依此類推。這種處理可以大大簡化規(guī)則匹配少漆。我們無需查看每一條聲明臼膏,只要從哈希表中提取元素的相關(guān)規(guī)則即可。這種優(yōu)化方法可排除掉 95% 以上規(guī)則示损,因此在匹配過程中根本就不用考慮這些規(guī)則了
5. 布局(layout)
呈現(xiàn)器布局:
呈現(xiàn)器在創(chuàng)建完成并添加到呈現(xiàn)樹時渗磅,并不包含位置和大小信息。計算這些值的過程稱為布局或重排检访。HTML 采用基于流的布局模型夺溢,這意味著大多數(shù)情況下只要一次遍歷就能計算出幾何信息。處于流中靠后位置元素通常不會影響靠前位置元素的幾何特征烛谊,因此布局可以按從左至右、從上至下的順序遍歷文檔嘉汰。根呈現(xiàn)器的位置左邊是 0,0丹禀,其尺寸為視口(也就是瀏覽器窗口的可見區(qū)域)。所有的呈現(xiàn)器都有一個“l(fā)ayout”或者“reflow”方法鞋怀,每一個呈現(xiàn)器都會調(diào)用其需要進行布局的子代的 layout 方法双泪。Dirty 位系統(tǒng) :
為避免對所有細小更改都進行整體布局,瀏覽器采用了一種dirty 位
系統(tǒng)密似。如果某個呈現(xiàn)器發(fā)生了更改焙矛,或者將自身及其子代標注為dirty
,則需要進行布局残腌。有兩種標記:dirty
和children are dirty
村斟。children are dirty
表示盡管呈現(xiàn)器自身沒有變化,但它至少有一個子代需要布局抛猫。-
全局布局和增量布局
- 全局布局: 是指觸發(fā)了整個呈現(xiàn)樹范圍的布局蟆盹,觸發(fā)原因可能包括:
- 影響所有呈現(xiàn)器的全局樣式更改,例如字體大小更改闺金。
- 屏幕大小調(diào)整逾滥。
- 增量布局: 可以采用增量方式,也就是只對 dirty 呈現(xiàn)器進行布局(這樣可能存在需要進行額外布局的弊端)败匹。
當呈現(xiàn)器為 dirty 時寨昙,會異步觸發(fā)增量布局。例如掀亩,當來自網(wǎng)絡的額外內(nèi)容添加到 DOM 樹之后舔哪,新的呈現(xiàn)器附加到了呈現(xiàn)樹中。
- 全局布局: 是指觸發(fā)了整個呈現(xiàn)樹范圍的布局蟆盹,觸發(fā)原因可能包括:
-
布局步驟:
- 父呈現(xiàn)器確定自己的寬度槽棍。
- 父呈現(xiàn)器依次處理子呈現(xiàn)器尸红,并且:
- 放置子呈現(xiàn)器(設置 x,y 坐標)。如果有必要,調(diào)用子呈現(xiàn)器的布局(如果子呈現(xiàn)器是 dirty 的外里,或者這是全局布局怎爵,或出于其他某些原因),這會計算子呈現(xiàn)器的高度盅蝗。
- 父呈現(xiàn)器根據(jù)子呈現(xiàn)器的累加高度以及邊距和補白的高度來設置自身高度鳖链,此值也可供父呈現(xiàn)器的父呈現(xiàn)器使用。
- 將其 dirty 位設置為 false墩莫。
6. 繪制(paint)
-
呈現(xiàn)器繪制: 本質(zhì)上就是填充像素的過程芙委。包括繪制文字、顏色狂秦、圖像灌侣、邊框和陰影等,也就是一個DOM元素所有的可視效果裂问。一般來說侧啼,這個繪制過程是在多個層上完成的。
在繪制階段堪簿,系統(tǒng)會遍歷呈現(xiàn)樹痊乾,并調(diào)用呈現(xiàn)器的“paint”方法,將呈現(xiàn)器的內(nèi)容顯示在屏幕上椭更。繪制工作是使用用戶界面基礎組件完成的哪审。 - 全局繪制和增量繪制
-
繪制順序: 繪制的順序其實就是元素進入堆棧樣式上下文的順序。這些堆棧會從后往前繪制虑瀑,因此這樣的順序會影響繪制湿滓。塊呈現(xiàn)器的堆棧順序如下:
- 背景顏色
- 背景圖片
- 邊框
- 子代
- 輪廓
三、瀏覽器事件模型
1. 呈現(xiàn)引擎的線程
呈現(xiàn)引擎采用了單線程舌狗。幾乎所有操作(除了網(wǎng)絡操作)都是在單線程中進行的茉稠。在 Firefox 和 Safari 中,該線程就是瀏覽器的主線程把夸。而在 Chrome 瀏覽器中而线,該線程是標簽進程的主線程。
2. 事件循環(huán):
瀏覽器的主線程是事件循環(huán)恋日。它是一個無限循環(huán)膀篮,永遠處于接受處理狀態(tài),并等待事件(如布局和繪制事件)發(fā)生岂膳,并進行處理誓竿。
3. Javascript單線程模式
為什么是單線程 : JavaScript的單線程,與它的用途有關(guān)谈截。作為瀏覽器腳本語言筷屡,JavaScript的主要用途是與用戶互動涧偷,以及操作DOM。這決定了它只能是單線程毙死,否則會帶來很復雜的同步問題燎潮。比如,假定JavaScript同時有兩個線程扼倘,一個線程在某個DOM節(jié)點上添加內(nèi)容确封,另一個線程刪除了這個節(jié)點,這時瀏覽器應該以哪個線程為準再菊?
任務隊列: 單線程就意味著爪喘,所有任務需要排隊,前一個任務結(jié)束纠拔,才會執(zhí)行后一個任務秉剑。如果前一個任務耗時很長,后一個任務就不得不一直等著稠诲。
如果排隊是因為計算量大侦鹏,CPU忙不過來,倒也算了吕粹,但是很多時候CPU是閑著的,因為IO設備(輸入輸出設備)很慢(比如Ajax操作從網(wǎng)絡讀取數(shù)據(jù))岗仑,不得不等著結(jié)果出來匹耕,再往下執(zhí)行。
JavaScript語言的設計者意識到荠雕,這時主線程完全可以不管IO設備稳其,掛起處于等待中的任務,先運行排在后面的任務炸卑。等到IO設備返回了結(jié)果既鞠,再回過頭,把掛起的任務繼續(xù)執(zhí)行下去盖文。
于是嘱蛋,所有任務可以分成兩種,一種是同步任務
五续,另一種是異步任務
洒敏。同步任務指的是,在主線程上排隊執(zhí)行的任務疙驾,只有前一個任務執(zhí)行完畢凶伙,才能執(zhí)行后一個任務;異步任務指的是它碎,不進入主線程函荣、而進入任務隊列
(task queue)的任務显押,只有"任務隊列"通知主線程,某個異步任務可以執(zhí)行了傻挂,該任務才會進入主線程執(zhí)行乘碑。-
Event Loop
四. 性能優(yōu)化及調(diào)試
1. 回顧網(wǎng)頁渲染過程:
- HTML代碼轉(zhuǎn)化成DOM
- CSS代碼轉(zhuǎn)化成CSSOM(CSS Object Model)
- 結(jié)合DOM和CSSOM,生成一棵渲染樹(包含每個節(jié)點的視覺信息)
- 生成布局(layout)踊谋,即將所有渲染樹的所有節(jié)點進行平面合成
- 將布局繪制(paint)在屏幕上
這五步里面蝉仇,第一步到第三步都非常快殖蚕,耗時的是第四步和第五步轿衔。
"生成布局"(flow)和"繪制"(paint)這兩步,合稱為"渲染"(render)睦疫。
2. 重排和重繪
網(wǎng)頁生成的時候害驹,至少會渲染一次。用戶訪問的過程中蛤育,還會不斷重新渲染宛官。
以下三種情況,會導致網(wǎng)頁重新渲染瓦糕。
- 修改DOM
- 修改樣式表
- 用戶事件(比如鼠標懸停底洗、頁面滾動、輸入框鍵入文字咕娄、改變窗口大小等等)
重新渲染亥揖,就需要重新生成布局和重新繪制。前者叫做"重排"(reflow)圣勒,后者叫做"重繪"(repaint)费变。
需要注意的是,"重繪"不一定需要"重排"圣贸,比如改變某個網(wǎng)頁元素的顏色挚歧,就只會觸發(fā)"重繪",不會觸發(fā)"重排"吁峻,因為布局沒有改變滑负。但是,"重排"必然導致"重繪"用含,比如改變一個網(wǎng)頁元素的位置橙困,就會同時觸發(fā)"重排"和"重繪",因為布局改變了耕餐。
3. 對于性能的影響
重排和重繪會不斷觸發(fā)凡傅,這是不可避免的。但是肠缔,它們非常耗費資源夏跷,是導致網(wǎng)頁性能低下的根本原因哼转。
提高網(wǎng)頁性能,就是要降低"重排"和"重繪"的頻率和成本槽华,盡量少觸發(fā)重新渲染壹蔓。
前面提到,DOM變動和樣式變動猫态,都會觸發(fā)重新渲染佣蓉。但是,瀏覽器已經(jīng)很智能了亲雪,會盡量把所有的變動集中在一起勇凭,排成一個隊列,然后一次性執(zhí)行义辕,盡量避免多次重新渲染虾标。
div.style.color = 'blue';
div.style.marginTop = '30px';
上面代碼中,div元素有兩個樣式變動灌砖,但是瀏覽器只會觸發(fā)一次重排和重繪璧函。
如果寫得不好,就會觸發(fā)兩次重排和重繪基显。
div.style.color = 'blue';
var margin = parseInt(div.style.marginTop);
div.style.marginTop = (margin + 10) + 'px';
上面代碼對div元素設置背景色以后蘸吓,第二行要求瀏覽器給出該元素的位置,所以瀏覽器不得不立即重排撩幽。
一般來說库继,樣式的寫操作之后,如果有下面這些屬性的讀操作摸航,都會引發(fā)瀏覽器立即重新渲染制跟。
- offsetTop/offsetLeft/offsetWidth/offsetHeight
- scrollTop/scrollLeft/scrollWidth/scrollHeight
- clientTop/clientLeft/clientWidth/clientHeight
- getComputedStyle()
所以舅桩,從性能角度考慮酱虎,盡量不要把讀操作和寫操作,放在一個語句里面擂涛。
// bad
div.style.left = div.offsetLeft + 10 + "px";
div.style.top = div.offsetTop + 10 + "px";
// good
var left = div.offsetLeft;
var top = div.offsetTop;
div.style.left = left + 10 + "px";
div.style.top = top + 10 + "px";
一般的規(guī)則是:
- 樣式表越簡單读串,重排和重繪就越快。
- 重排和重繪的DOM元素層級越高撒妈,成本就越高恢暖。
- table元素的重排和重繪成本,要高于div元素
4狰右、提高性能的九個技巧
有一些技巧杰捂,可以降低瀏覽器重新渲染的頻率和成本。
第一條: 是上一節(jié)說到的棋蚌,DOM 的多個讀操作(或多個寫操作)嫁佳,應該放在一起挨队。不要兩個讀操作之間,加入一個寫操作蒿往。
第二條:如果某個樣式是通過重排得到的盛垦,那么最好緩存結(jié)果。避免下一次用到的時候瓤漏,瀏覽器又要重排腾夯。
-
第三條: 不要一條條地改變樣式,而要通過改變class蔬充,或者csstext屬性蝶俱,一次性地改變樣式。
// bad var left = 10; var top = 10; el.style.left = left + "px"; el.style.top = top + "px"; // good el.className += " theclassname"; // good el.style.cssText += "; left: " + left + "px; top: " + top + "px;";
第四條: 盡量使用離線DOM娃惯,而不是真實的網(wǎng)面DOM跷乐,來改變元素樣式。比如趾浅,操作Document Fragment對象愕提,完成后再把這個對象加入DOM。再比如皿哨,使用 cloneNode() 方法浅侨,在克隆的節(jié)點上進行操作,然后再用克隆的節(jié)點替換原始節(jié)點证膨。
第五條:先將元素設為display: none(需要1次重排和重繪)如输,然后對這個節(jié)點進行100次操作,最后再恢復顯示(需要1次重排和重繪)央勒。這樣一來不见,你就用兩次重新渲染,取代了可能高達100次的重新渲染崔步。
第六條: position屬性為absolute或fixed的元素稳吮,重排的開銷會比較小,因為不用考慮它對其他元素的影響井濒。
第七條: 只在必要的時候灶似,才將元素的display屬性為可見,因為不可見的元素不影響重排和重繪瑞你。另外酪惭,visibility : hidden的元素只對重繪有影響,不影響重排者甲。
第八條: 使用虛擬DOM的腳本庫春感,比如React等。
第九條:使用 window.requestAnimationFrame()虏缸、window.requestIdleCallback() 這兩個方法調(diào)節(jié)重新渲染鲫懒。
五. 附錄
1. 渲染總流程圖
2. 參考文章
- http://taligarsiel.com/Projects/howbrowserswork1.htm
- https://zhuanlan.zhihu.com/p/30134423?utm_source=wechat_session&utm_medium=social
- https://zhuanlan.zhihu.com/p/24911872?utm_source=wechat_session&utm_medium=social
- http://www.ruanyifeng.com/blog/2014/10/event-loop.html
- https://www.html5rocks.com/zh/tutorials/internals/howbrowserswork/#Introduction
- http://www.ruanyifeng.com/blog/2015/09/web-page-performance-in-depth.html
- http://blog.cssforest.org/2012/02/08/%E6%B5%8F%E8%A7%88%E5%99%A8%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86%E6%B5%85%E6%9E%90.html