瀏覽器渲染原理及性能優(yōu)化

一. 瀏覽器簡介

1. 瀏覽器種類

  • 目前使用的主流瀏覽器有五個:Internet Explorer族淮、Firefox袱衷、Safari聪廉、Chrome 瀏覽器和 Opera。本文中以開放源代碼瀏覽器為例,即 Firefox类浪、Chrome 瀏覽器和 Safari(部分開源)。

2. 瀏覽器結(jié)構(gòu)

  1. 用戶界面 : 包括地址欄、前進/后退按鈕心肪、書簽菜單等。除了瀏覽器主窗口顯示的您請求的頁面外纠吴,其他顯示的各個部分都屬于用戶界面硬鞍。
  2. 瀏覽器引擎 : 在用戶界面和呈現(xiàn)引擎之間傳送指令。
  3. 呈現(xiàn)引擎 : 負責顯示請求的內(nèi)容呜象。如果請求的內(nèi)容是 HTML膳凝,它就負責解析 HTML 和 CSS 內(nèi)容,并將解析后的內(nèi)容顯示在屏幕上恭陡。
  4. 網(wǎng)絡 : 用于網(wǎng)絡調(diào)用蹬音,比如 HTTP 請求。其接口與平臺無關(guān)休玩,并為所有平臺提供底層實現(xiàn)著淆。
  5. 用戶界面后端 : 用于繪制基本的窗口小部件,比如組合框和窗口拴疤。其公開了與平臺無關(guān)的通用接口永部,而在底層使用操作系統(tǒng)的用戶界面方法。
  6. JavaScript 解釋器 : 用于解析和執(zhí)行 JavaScript 代碼呐矾。
  7. 數(shù)據(jù)存儲 : 這是持久層苔埋。瀏覽器需要在硬盤上保存各種數(shù)據(jù),例如 Cookie蜒犯。新的 HTML 規(guī)范 (HTML5) 定義了“網(wǎng)絡數(shù)據(jù)庫”组橄,這是一個完整(但是輕便)的瀏覽器內(nèi)數(shù)據(jù)庫荞膘。
  • 瀏覽器結(jié)構(gòu)圖

3. 渲染流程簡介

  1. 呈現(xiàn)引擎將開始解析 HTML 文檔,并將各標記逐個轉(zhuǎn)化成“內(nèi)容樹”上的 DOM 節(jié)點玉工。同時也會解析外部 CSS 文件以及樣式元素中的樣式數(shù)據(jù)羽资。HTML 中這些帶有視覺指令的樣式信息將用于創(chuàng)建另一個樹結(jié)構(gòu):呈現(xiàn)樹.
  2. 呈現(xiàn)樹包含多個帶有視覺屬性(如顏色和尺寸)的矩形。這些矩形的排列順序就是它們將在屏幕上顯示的順序遵班。
  3. 呈現(xiàn)樹構(gòu)建完畢之后屠升,進入“布局”處理階段,也就是為每個節(jié)點分配一個應出現(xiàn)在屏幕上的確切坐標狭郑。下一個階段是繪制 - 呈現(xiàn)引擎會遍歷呈現(xiàn)樹腹暖,由用戶界面后端層將每個節(jié)點繪制出來。
  • 渲染主流程圖

二. 瀏覽器渲染流程詳解

1. 解析

  1. 什么是解析:解析是呈現(xiàn)引擎中非常重要的一個環(huán)節(jié)愿阐,解析文檔是指將文檔轉(zhuǎn)化成為有意義的結(jié)構(gòu)微服,也就是可讓代碼理解和使用的結(jié)構(gòu)。解析得到的結(jié)果通常是代表了文檔結(jié)構(gòu)的節(jié)點樹缨历,它稱作解析樹或者語法樹以蕴。

  2. 解析的過程:詞法分析和語法分析

    • 詞法分析: 將輸入內(nèi)容分割成大量標記的過程。標記是語言中的詞匯辛孵,即構(gòu)成內(nèi)容的單位丛肮。在人類語言中,它相當于語言字典中的單詞魄缚。
    • 語法分析: 是應用語言的語法規(guī)則的過程宝与。
  3. 解析示例

    1. 文檔:

      2 + 3 - 1
      
    2. 詞法定義:詞匯通常用==正則表達式==表示

      詞法:我們用的語言可包含整數(shù)、加號和減號

      INTEGER :0|[1-9][0-9]* 
      PLUS : +
      MINUS: -
      
    3. 語法定義:語法通常使用一種稱為==BNF==的格式來定義

      1. 構(gòu)成語言的語法單位是表達式冶匹、項和運算符习劫。
      2. 我們用的語言可以包含任意數(shù)量的表達式。
      3. 表達式的定義是:一個“項”接一個“運算符”嚼隘,然后再接一個“項”诽里。
      4. 運算符是加號或減號。
      5. 項是一個整數(shù)或一個表達式
      expression :=  term  operation  term
      operation :=  PLUS | MINUS
      term := INTEGER | expression
      
    4. 解析樹:

      • 讓我們分析一下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)建

  1. 文檔:

    <!DOCTYPE html>
    <html>
    <body>
        <div>
            <h1 class="title">demo</h1>
            <input value="hello">
        </div>
    </body>
    </html>
    
  2. 詞法分析: 瀏覽器中的詞法分析器負責將輸入內(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: "
    
  1. 語法分析:瀏覽器中的解析器負責根據(jù)語言的語法規(guī)則分析文檔的結(jié)構(gòu)诱告,從而構(gòu)建解析樹, HTML 的定義采用了 ==DTD== 格式。此格式可用于定義 SGML 族的語言民晒。它包括所有允許使用的元素及其屬性和層次結(jié)構(gòu)的定義
  • 樹構(gòu)建算法
    <html>
        <body>
            Hello world
        </body>
    </html>
    
    樹構(gòu)建階段的輸入是一個來自標記化階段的標記序列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é)束孔祸。
  1. DOM解析樹

3. CSSOM樹構(gòu)建

  1. 文檔:
    p, div {
        margin-top: 3px;
    }
    .error {
        color: red;
    }
    
  2. 詞法:
    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}*
    
  3. 語法:
    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*] ')' ]
      ;
    
  4. 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)器鸠踪。

  1. 呈現(xiàn)樹和DOM樹對照關(guān)系:
呈現(xiàn)樹和DOM樹對照關(guān)系
呈現(xiàn)樹和DOM樹對照關(guān)系
  1. 呈現(xiàn)樹構(gòu)建示例:

    1. 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>
    
    1. 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
        }
    
    1. 顯現(xiàn)樹樣式計算:使用規(guī)則樹計算樣式上下文樹
    • 樣式上下文樹: Firefox 還采用了樣式上下文樹, WebKit 也有樣式對象,但它們不是保存在類似樣式上下文樹這樣的樹結(jié)構(gòu)中复斥,只是由 DOM 節(jié)點指向此類對象的相關(guān)樣式慢哈。
    • 規(guī)則樹: 所有匹配的規(guī)則都存儲在樹中。路徑中的底層節(jié)點擁有較高的優(yōu)先級永票。規(guī)則樹包含了所有已知規(guī)則匹配的路徑卵贱。規(guī)則的存儲是延遲進行的。規(guī)則樹不會在開始的時候就為所有的節(jié)點進行計算侣集,而是只有當某個節(jié)點樣式需要進行計算時键俱,才會向規(guī)則樹添加計算的路徑。
      使用規(guī)則樹計算樣式上下文樹
      使用規(guī)則樹計算樣式上下文樹

      chrome中查看樣式
      chrome中查看樣式

    樣式表解析完畢后世分,系統(tǒng)會根據(jù)選擇器將 CSS規(guī)則添加到某個哈希表中编振。這些哈希表的選擇器各不相同,包括ID臭埋、類名稱踪央、標記名稱等,還有一種通用哈希表瓢阴,適合不屬于上述類別的規(guī)則畅蹂。如果選擇器是 ID,規(guī)則就會添加到 ID 表中荣恐;如果選擇器是類液斜,規(guī)則就會添加到類表中累贤,依此類推。這種處理可以大大簡化規(guī)則匹配少漆。我們無需查看每一條聲明臼膏,只要從哈希表中提取元素的相關(guān)規(guī)則即可。這種優(yōu)化方法可排除掉 95% 以上規(guī)則示损,因此在匹配過程中根本就不用考慮這些規(guī)則了

5. 布局(layout)

  1. 呈現(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 方法双泪。

  2. Dirty 位系統(tǒng) :
    為避免對所有細小更改都進行整體布局,瀏覽器采用了一種dirty 位系統(tǒng)密似。如果某個呈現(xiàn)器發(fā)生了更改焙矛,或者將自身及其子代標注為dirty,則需要進行布局残腌。有兩種標記:dirtychildren are dirty村斟。children are dirty表示盡管呈現(xiàn)器自身沒有變化,但它至少有一個子代需要布局抛猫。

  3. 全局布局和增量布局

    1. 全局布局: 是指觸發(fā)了整個呈現(xiàn)樹范圍的布局蟆盹,觸發(fā)原因可能包括:
      1. 影響所有呈現(xiàn)器的全局樣式更改,例如字體大小更改闺金。
      2. 屏幕大小調(diào)整逾滥。
    2. 增量布局: 可以采用增量方式,也就是只對 dirty 呈現(xiàn)器進行布局(這樣可能存在需要進行額外布局的弊端)败匹。
      當呈現(xiàn)器為 dirty 時寨昙,會異步觸發(fā)增量布局。例如掀亩,當來自網(wǎng)絡的額外內(nèi)容添加到 DOM 樹之后舔哪,新的呈現(xiàn)器附加到了呈現(xiàn)樹中。
  4. 布局步驟:

    1. 父呈現(xiàn)器確定自己的寬度槽棍。
    2. 父呈現(xiàn)器依次處理子呈現(xiàn)器尸红,并且:
    3. 放置子呈現(xiàn)器(設置 x,y 坐標)。如果有必要,調(diào)用子呈現(xiàn)器的布局(如果子呈現(xiàn)器是 dirty 的外里,或者這是全局布局怎爵,或出于其他某些原因),這會計算子呈現(xiàn)器的高度盅蝗。
    4. 父呈現(xiàn)器根據(jù)子呈現(xiàn)器的累加高度以及邊距和補白的高度來設置自身高度鳖链,此值也可供父呈現(xiàn)器的父呈現(xiàn)器使用。
    5. 將其 dirty 位設置為 false墩莫。

6. 繪制(paint)

  1. 呈現(xiàn)器繪制: 本質(zhì)上就是填充像素的過程芙委。包括繪制文字、顏色狂秦、圖像灌侣、邊框和陰影等,也就是一個DOM元素所有的可視效果裂问。一般來說侧啼,這個繪制過程是在多個層上完成的。
    在繪制階段堪簿,系統(tǒng)會遍歷呈現(xiàn)樹痊乾,并調(diào)用呈現(xiàn)器的“paint”方法,將呈現(xiàn)器的內(nèi)容顯示在屏幕上椭更。繪制工作是使用用戶界面基礎組件完成的哪审。
  2. 全局繪制和增量繪制
  3. 繪制順序: 繪制的順序其實就是元素進入堆棧樣式上下文的順序。這些堆棧會從后往前繪制虑瀑,因此這樣的順序會影響繪制湿滓。塊呈現(xiàn)器的堆棧順序如下:
    1. 背景顏色
    2. 背景圖片
    3. 邊框
    4. 子代
    5. 輪廓

三、瀏覽器事件模型

1. 呈現(xiàn)引擎的線程

呈現(xiàn)引擎采用了單線程舌狗。幾乎所有操作(除了網(wǎng)絡操作)都是在單線程中進行的茉稠。在 Firefox 和 Safari 中,該線程就是瀏覽器的主線程把夸。而在 Chrome 瀏覽器中而线,該線程是標簽進程的主線程。

2. 事件循環(huán):

瀏覽器的主線程是事件循環(huán)恋日。它是一個無限循環(huán)膀篮,永遠處于接受處理狀態(tài),并等待事件(如布局和繪制事件)發(fā)生岂膳,并進行處理誓竿。

3. Javascript單線程模式

  1. 為什么是單線程 : JavaScript的單線程,與它的用途有關(guān)谈截。作為瀏覽器腳本語言筷屡,JavaScript的主要用途是與用戶互動涧偷,以及操作DOM。這決定了它只能是單線程毙死,否則會帶來很復雜的同步問題燎潮。比如,假定JavaScript同時有兩個線程扼倘,一個線程在某個DOM節(jié)點上添加內(nèi)容确封,另一個線程刪除了這個節(jié)點,這時瀏覽器應該以哪個線程為準再菊?

  2. 任務隊列: 單線程就意味著爪喘,所有任務需要排隊,前一個任務結(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í)行乘碑。

  3. Event Loop

    jsEventLoop
    jsEventLoop

四. 性能優(yōu)化及調(diào)試

1. 回顧網(wǎng)頁渲染過程:

  1. HTML代碼轉(zhuǎn)化成DOM
  2. CSS代碼轉(zhuǎn)化成CSSOM(CSS Object Model)
  3. 結(jié)合DOM和CSSOM,生成一棵渲染樹(包含每個節(jié)點的視覺信息)
  4. 生成布局(layout)踊谋,即將所有渲染樹的所有節(jié)點進行平面合成
  5. 將布局繪制(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狰右、提高性能的九個技巧

有一些技巧杰捂,可以降低瀏覽器重新渲染的頻率和成本。

  1. 第一條: 是上一節(jié)說到的棋蚌,DOM 的多個讀操作(或多個寫操作)嫁佳,應該放在一起挨队。不要兩個讀操作之間,加入一個寫操作蒿往。

  2. 第二條:如果某個樣式是通過重排得到的盛垦,那么最好緩存結(jié)果。避免下一次用到的時候瓤漏,瀏覽器又要重排腾夯。

  3. 第三條: 不要一條條地改變樣式,而要通過改變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;";
    
  1. 第四條: 盡量使用離線DOM娃惯,而不是真實的網(wǎng)面DOM跷乐,來改變元素樣式。比如趾浅,操作Document Fragment對象愕提,完成后再把這個對象加入DOM。再比如皿哨,使用 cloneNode() 方法浅侨,在克隆的節(jié)點上進行操作,然后再用克隆的節(jié)點替換原始節(jié)點证膨。

  2. 第五條:先將元素設為display: none(需要1次重排和重繪)如输,然后對這個節(jié)點進行100次操作,最后再恢復顯示(需要1次重排和重繪)央勒。這樣一來不见,你就用兩次重新渲染,取代了可能高達100次的重新渲染崔步。

  3. 第六條: position屬性為absolute或fixed的元素稳吮,重排的開銷會比較小,因為不用考慮它對其他元素的影響井濒。

  4. 第七條: 只在必要的時候灶似,才將元素的display屬性為可見,因為不可見的元素不影響重排和重繪瑞你。另外酪惭,visibility : hidden的元素只對重繪有影響,不影響重排者甲。

  5. 第八條: 使用虛擬DOM的腳本庫春感,比如React等。

  6. 第九條:使用 window.requestAnimationFrame()虏缸、window.requestIdleCallback() 這兩個方法調(diào)節(jié)重新渲染鲫懒。

五. 附錄

1. 渲染總流程圖

瀏覽器渲染流程
瀏覽器渲染流程

2. 參考文章

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末纺铭,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子刀疙,更是在濱河造成了極大的恐慌舶赔,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件谦秧,死亡現(xiàn)場離奇詭異竟纳,居然都是意外死亡,警方通過查閱死者的電腦和手機疚鲤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進店門锥累,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人集歇,你說我怎么就攤上這事桶略。” “怎么了诲宇?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵际歼,是天一觀的道長。 經(jīng)常有香客問我姑蓝,道長鹅心,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任纺荧,我火速辦了婚禮旭愧,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘宙暇。我一直安慰自己输枯,他們只是感情好,可當我...
    茶點故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布占贫。 她就那樣靜靜地躺著桃熄,像睡著了一般。 火紅的嫁衣襯著肌膚如雪靶剑。 梳的紋絲不亂的頭發(fā)上蜻拨,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天池充,我揣著相機與錄音桩引,去河邊找鬼。 笑死收夸,一個胖子當著我的面吹牛坑匠,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播卧惜,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼厘灼,長吁一口氣:“原來是場噩夢啊……” “哼夹纫!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起设凹,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤舰讹,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后闪朱,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體月匣,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年奋姿,在試婚紗的時候發(fā)現(xiàn)自己被綠了锄开。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡称诗,死狀恐怖萍悴,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情寓免,我是刑警寧澤癣诱,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站袜香,受9級特大地震影響狡刘,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜困鸥,卻給世界環(huán)境...
    茶點故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一嗅蔬、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧疾就,春花似錦澜术、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至姑荷,卻和暖如春盒延,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背鼠冕。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工添寺, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人懈费。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓计露,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子票罐,可洞房花燭夜當晚...
    茶點故事閱讀 44,611評論 2 353

推薦閱讀更多精彩內(nèi)容