sponsored links

畫了20張圖,詳解瀏覽器渲染引擎工作原理

今天我們來學習一下瀏覽器渲染引擎的工作原理,文章內容較多,建議先收藏再學習!

先來看看Chrome瀏覽器的架構圖:

畫了20張圖,詳解瀏覽器渲染引擎工作原理

通常,我們編寫的HTML、CSS、JavaScript等檔案,經過瀏覽器執行之後就會顯示出頁面,那他們是如何轉化為頁面的?這背後的原理是什麼?這個過程就是瀏覽器的渲染程序來操作實現的。瀏覽器的渲染程序的主要任務就是將靜態資源轉化為視覺化介面:

對於中間的瀏覽器,它就是一個黑盒,下面就來看看這個黑盒是如何將靜態資源轉化為前端介面的。由於渲染機制比較複雜,所以渲染模組在執行過程中會被劃分為很多子階段,輸入的靜態資源經過這些子階段,最後輸出頁面。我們將一個處理流程稱為渲染流水線,其大致流程如下圖所示:

畫了20張圖,詳解瀏覽器渲染引擎工作原理

這裡主要包含五個過程:

  • DOM樹構建:渲染引擎使用HTML解析器(呼叫XML解析器)解析HTML文件,將各個HTML元素逐個轉化成DOM節點,從而生成DOM樹;
  • CSSOM樹構建:CSS解析器解析CSS,並將其轉化為CSS物件,將這些CSS物件組裝起來,構建CSSOM樹;
  • 渲染樹構建:DOM 樹和 CSSOM 樹都構建完成以後,瀏覽器會根據這兩棵樹構建出一棵渲染樹;
  • 頁面佈局:渲染樹構建完畢之後,元素的位置關係以及需要應用的樣式就確定了,這時瀏覽器會計算出所有元素的大小和絕對位置;
  • 頁面繪製:頁面佈局完成之後,瀏覽器會將根據處理出來的結果,把每一個頁面圖層轉換為畫素,並對所有的媒體檔案進行解碼。

對於這五個流程,每一階段都有對應的產物,分別是:DOM樹、CSSOM樹、渲染樹、盒模型、介面。

下圖為渲染引擎工作流程中各個步驟所對應的模組:

畫了20張圖,詳解瀏覽器渲染引擎工作原理

從圖中可以看出,渲染引擎主要包含的模組有:

  • HTML解析器:解析HTML文件,主要作用是將HTML文件轉換成DOM樹;
  • CSS解析器:將DOM中的各個元素物件進行計算,獲取樣式資訊,用於渲染樹的構建;
  • JavaScript直譯器:使用JavaScript可以修改網頁的內容、CSS規則等。JavaScript直譯器能夠解釋JavaScript程式碼,並透過DOM介面和CSSOM介面來修改網頁內容、樣式規則,從而改變渲染結果;
  • 頁面佈局:DOM建立之後,渲染引擎將其中的元素物件與樣式規則進行結合,可以得到渲染樹。佈局則是針對渲染樹,計算其各個元素的大小、位置等佈局資訊。
  • 頁面繪製:使用圖形庫將佈局計算後的渲染樹繪製成視覺化的影象結果。

下面就分別來看看這些過程都做了哪些操作。

一、DOM樹構建

在說構建DOM樹之前,我們首先需要知道,為什麼要構建DOM樹呢? 這是因為,瀏覽器是無法直接理解和使用HTML的,所以需要將HTML轉化為瀏覽器能夠理解的結構——DOM樹。

瞭解過資料結構的小夥伴對於樹結構應該不陌生,樹是由結點或頂點和邊組成的且不存在著任何環的一種資料結構。一棵非空的樹包括一個根結點,還有多個附加結點,所有結點構成一個多級分層結構。下面透過一張圖來看看什麼是樹結構:

畫了20張圖,詳解瀏覽器渲染引擎工作原理

對於上面的三個結構,前兩個都是樹,他們都只有唯一的根節點,而且不存在環結構。而第三個存在環,所以就不是一個樹結構。

說完樹結構,就回歸正題,來看看什麼是DOM樹。在頁面中,每個HTML標籤都會被瀏覽器解析成文件物件。HTML本質上就是一個巢狀結構,在解析時會把每個文件物件用一個樹形結構組織起來,所有的文件物件都會掛在document上,這種組織方式就是HTML最基礎的結構——文件物件模型(DOM),這棵樹的每個文件物件就叫做DOM節點。

在渲染引擎中,DOM 有三個層面的作用:

  • 從頁面的視角來看,DOM 是生成頁面的基礎資料結構;
  • 從 JavaScript 指令碼視角來看,DOM 提供給 JavaScript 指令碼操作的介面,透過這套介面,JavaScript 可以對 DOM 結構進行訪問,從而改變文件的結構、樣式和內容;
  • 從安全視角來看,DOM 是一道安全防護線,一些不安全的內容在 DOM 解析階段會被拒之門外。

在渲染引擎內部,HTML 解析器負責將 HTML 位元組流轉換為 DOM 結構,其轉化過程如下:

畫了20張圖,詳解瀏覽器渲染引擎工作原理

1. 字元流→詞(token)

HTML結構會首先透過分詞器將位元組流拆分為詞(token)。Token分為Tag Token 和文字 Token。下面來看一個HTML程式碼是如何被拆分的:

<body>
    <div>
        <p>hello world</p>
    </div>
</body>
複製程式碼

對於這句程式碼,可以拆成詞:

畫了20張圖,詳解瀏覽器渲染引擎工作原理

可以看到,Tag Token 又分 StartTag 和 EndTag,<body>、<div>、<p>就是 StartTag ,</body>、</div>、</p>就是 EndTag,分別對應圖中的藍色和紅色塊,文字 Token 對應綠色塊。 ​

這裡會透過狀態機將字元拆分成token,所謂的狀態機就是將每個詞的特徵逐個拆分成獨立的狀態,然後再將所有詞的特徵字符合並起來,形成一個連通的圖結構。那為什麼要使用狀態機呢?因為每讀取一個字元,都要做一次決策,這些決策都和當前的狀態有關。 ​

實際上,狀態機的作用就是用來做詞法分析的,將字元流分解為詞(token)。

2. 詞(token)→DOM樹

接下來就需要將 Token 解析為 DOM 節點,並將 DOM 節點新增到 DOM 樹中。這個過程是透過棧結構來實現的,這個棧主要用來計算節點之間的父子關係,上面步驟中生成的token會按順序壓入棧中,該過程的規則如下:

  • 如果分詞器解析出來是StartTag Token,HTML 解析器會為該 Token 建立一個 DOM 節點,然後將該節點加入到 DOM 樹中,它的父節點就是棧中相鄰的那個元素生成的節點;
  • 如果分詞器解析出來是 文字 Token,那麼會生成一個文字節點,然後將該節點加入到 DOM 樹中,文字 Token 是不需要壓入到棧中,它的父節點就是當前棧頂 Token 所對應的 DOM 節點;
  • 如果分詞器解析出來的是EndTag Token,比如是 EndTag div,HTML 解析器會檢視 Token 棧頂的元素是否是 StarTag div,如果是,就將 StartTag div從棧中彈出,表示該 div 元素解析完成。

透過分詞器產生的新 Token 就這樣不停地入棧和出棧,整個解析過程就這樣一直持續下去,直到分詞器將所有位元組流分詞完成。

下面來看看這的Token棧是如何工作的,有如下HTML結構:

<html>
    <body>
        <div>hello juejin</div>
        <p>hello world</p>
    </body>
</html>
複製程式碼

開始時,HTML解析器會建立一個根為 document 的空的 DOM 結構,同時將 StartTag document 的Token壓入棧中,然後再將解析出來的第一個 StartTag html 壓入棧中,並建立一個 html 的DOM節點,新增到document上,這時Token棧和DOM樹如下:

畫了20張圖,詳解瀏覽器渲染引擎工作原理

接下來body和div標籤也會和上面的過程一樣,進行入棧操作:

畫了20張圖,詳解瀏覽器渲染引擎工作原理

隨後就會解析到 div標籤中的文字Token,渲染引擎會為該 Token 建立一個文字節點,並將該 Token 新增到 DOM 中,它的父節點就是當前 Token 棧頂元素對應的節點:

畫了20張圖,詳解瀏覽器渲染引擎工作原理

接下來就是第一個EndTag div,這時 HTML 解析器會判斷當前棧頂元素是否是 StartTag div,如果是,則從棧頂彈出 StartTag div,如下圖所示:

再之後的過程就和上面類似了,最終的結果如下:

畫了20張圖,詳解瀏覽器渲染引擎工作原理

二、CSSOM樹構建

上面已經基本瞭解了DOM的構建過程,但是這個DOM結構只包含節點,並不包含任何的樣式資訊。下面就來看看,瀏覽器是如何把CSS樣式應用到DOM節點上的。

同樣,瀏覽器也是無法直接理解CSS程式碼的,需要將其瀏覽器可以理解的CSSOM樹。實際上。瀏覽器在構建 DOM 樹的同時,如果樣式也載入完成了,那麼 CSSOM 樹也會同步構建。CSSOM 樹和 DOM 樹類似,它主要有兩個作用:

  • 提供給 JavaScript 操作樣式的能力;
  • 為渲染樹的合成提供基礎的樣式資訊。

不過,CSSOM 樹和 DOM 樹是獨立的兩個資料結構,它們並沒有一一對應關係。DOM 樹描述的是 HTML 標籤的層級關係,CSSOM 樹描述的是選擇器之間的層級關係。可以在瀏覽器的控制檯,透過document.styleSheets命令來檢視CSSOM樹:

那CSS樣式的來源有哪些呢?

可以看到,CSS樣式的來源主要有三種:

  • 透過 link 引用的外部 CSS 樣式檔案;
  • <style>標籤內的CSS樣式;
  • 元素的style屬性內嵌的CSS。

在將CSS轉化為樹形物件之前,還需要將樣式表中的屬性值進行標準化處理,比如,當遇到以下CSS樣式:

body { font-size: 2em }
p {color:blue;}
div {font-weight: bold}
div p {color:green;}
div {color:red; }
複製程式碼

可以看到上面CSS中有很多屬性值,比如2em、blue、red、bold等,這些數值並不能被瀏覽器直接理解。所以,需要將所有值轉化為瀏覽器渲染引擎容易理解的、標準化的計算值,這個過程就是屬性值標準化。經過標準化的過程,上面的程式碼會變成這樣:

body { font-size: 32px }
p {color: rgb(0, 0, 255);}
div {font-weight: 700}
div p {color: (0, 128, 0);}
div {color: (255, 0, 0); }
複製程式碼

可以看到,2em被解析成了32px,blue被解析成了rgb(255, 0, 0),bold被解析成700。現在樣式的屬性已被標準化了,接下來就需要計算 DOM 樹中每個節點的樣式屬性了,這就涉及到 CSS 的繼承規則和層疊規則。

(1)樣式繼承

在 CSS 中存在樣式的繼承機制,CSS 繼承就是每個 DOM 節點都包含有父節點的樣式。比如在 HTML 上設定“font-size:20px;”,那麼頁面裡基本所有的標籤都可以繼承到這個屬性了。

在CSS中,有繼承性的屬性主要有以下幾種:

  1. 字體系列屬性
  • font-family:字體系列
  • font-weight:字型的粗細
  • font-size:字型的大小
  • font-style:字型的風格
  1. 文本系列屬性
  • text-indent:文字縮排
  • text-align:文字水平對齊
  • line-height:行高
  • word-spacing:單詞之間的間距
  • letter-spacing:中文或者字母之間的間距
  • text-transform:控制文字大小寫(就是uppercase、lowercase、capitalize這三個)
  • color:文字顏色
  1. 元素可見性
  • visibility:控制元素顯示隱藏
  1. 列表佈局屬性
  • list-style:列表風格,包括list-style-type、list-style-image等
  1. 游標屬性
  • cursor:游標顯示為何種形態

(2)樣式層疊

樣式計算過程中的第二個規則是樣式層疊。層疊是 CSS 的一個基本特徵,它是一個定義了 如何合併來自多個源的屬性值的演算法。它在 CSS 處於核心地位,CSS 的全稱“層疊樣式表”正是強調了這一點。這裡不再多說。

總之,樣式計算階段的目的是為了計算出 DOM 節點中每個元素的具體樣式,在計算過程 中需要遵守 CSS 的繼承和層疊兩個規則。這個階段最終輸出的內容是每個 DOM 節點的樣 式,並被儲存在 ComputedStyle 的結構內。

對於以下程式碼:

<html>
    <head>
        <link href="./style.css">
        <style>
            .juejin {
                width: 100px;
                height: 50px;
                background: red;
            }

            .content {
                font-size: 25px;
                line-height: 25px;
                margin: 10px;
            }
        </style>
    </head>
    <body>
        <div class="juejin">
            <div>CUGGZ</div>
        </div>
        <p style="color: blue" class="content">
            <span>hello world</span>
            <p style="display: none;">瀏覽器</p>
        </p>
    </body>
</html>
複製程式碼

最終生成的CSSOM樹大致如下:

畫了20張圖,詳解瀏覽器渲染引擎工作原理

三、渲染樹構建

在 DOM 樹和 CSSOM 樹都渲染完成之後,就會進入渲染樹的構建階段。渲染樹就是 DOM 樹和 CSSOM 樹的結合,會得到一個可以知道每個節點會應用什麼樣式的資料結構。這個結合的過程就是遍歷整個 DOM 樹,然後在 CSSOM 樹裡查詢到匹配的樣式。

在不同瀏覽器裡,構建渲染樹的過程不太一樣:

  • 在 Chrome 裡會在每個節點上使用 attach() 方法,把 CSSOM 樹的節點掛在 DOM 樹上作為渲染樹。
  • 在 Firefox 裡會單獨構造一個新的結構, 用來連線 DOM 樹和 CSSOM 樹的對映關係。

那為什麼要構建渲染樹呢?在上面的示例中可以看到,DOM樹可能包含一些不可見的元素,比如head標籤,使用display:none;屬性的元素等。所以在顯示頁面之前,還要額外地構建一棵只包含可見元素的渲染樹

下面來看看構建渲染樹的過程:

畫了20張圖,詳解瀏覽器渲染引擎工作原理

可以看到,DOM樹中不可見的節點都沒有包含到渲染樹中。為了構建渲染樹,瀏覽器上大致做了如下工作:遍歷DOM樹中所有可見節點,並把這些節點加到佈局中,而不可見的節點會被佈局樹忽略掉,如 head 標籤下面的全部內容,再比如 p.p 這個元素,因為它的屬性包含 dispaly:none,所以這個元素也沒有被包含進渲染樹中。如果給元素設定了visibility: hidden;屬性,那這個元素會出現在渲染樹中,因為具有這個樣式的元素是需要佔位的,只不過不需要顯示出來。 ​

這裡在查詢的過程中,出於效率的考慮,會從 CSSOM 樹的葉子節點開始查詢,對應在 CSS 選擇器上也就是從選擇器的最右側向左查詢。所以,不建議使用標籤選擇器和萬用字元選擇器來定義元素樣式。 ​

除此之外,同一個 DOM 節點可能會匹配到多個 CSSOM 節點,而最終的效果由哪個 CSS 規則來確定,就是樣式優先順序的問題了。當一個 DOM 元素受到多條樣式控制時,樣式的優先順序順序如下:內聯樣式 > ID選擇器 > 類選擇器 > 標籤選擇器 > 通用選擇器 > 繼承樣式 > 瀏覽器預設樣式

CSS常見選擇器的優先順序如下:


選擇器


格式


優先順序權重


id選擇器


#id


100


類選擇器


.classname


10


屬性選擇器


a[ref=“eee”]


10


偽類選擇器


li:last-child


10


標籤選擇器


div


1


偽元素選擇器


li:after


1


相鄰兄弟選擇器


h1+p


0


子選擇器


ul>li


0


後代選擇器


li a


0


萬用字元選擇器


*


0

對於選擇器的優先順序

  • 標籤選擇器、偽元素選擇器:1;
  • 類選擇器、偽類選擇器、屬性選擇器:10;
  • id 選擇器:100;
  • 內聯樣式:1000;

注意:

  • !important宣告的樣式的優先順序最高;
  • 如果優先順序相同,則最後出現的樣式生效;
  • 繼承得到的樣式的優先順序最低;

四、頁面佈局

經過上面的步驟,就生成了一棵渲染樹,這棵樹就是展示頁面的關鍵。到現在為止,已經有了需要渲染的所有節點之間的結構關係及其樣式資訊。下面就需要進行頁面的佈局。

透過計算渲染樹上每個節點的樣式,就能得出來每個元素所佔空間的大小和位置。當有了所有元素的大小和位置後,就可以在瀏覽器的頁面區域裡去繪製元素的邊框了。這個過程就是佈局。這個過程中,瀏覽器對渲染樹進行遍歷,將元素間巢狀關係以盒模型的形式寫入文件流:

畫了20張圖,詳解瀏覽器渲染引擎工作原理

盒模型在佈局過程中會計算出元素確切的大小和定位。計算完畢後,相應的資訊被寫回渲染樹上,就形成了佈局渲染樹。同時,每一個元素盒子也都攜帶著自身的樣式資訊,作為後續繪製的依據。

五、頁面繪製

1. 構建圖層

經過佈局,每個元素的位置和大小就有了,那下面是不是就該開始繪製頁面了?答案是否定的,因為頁面上可能有很多複雜的場景,比如3D變化、頁面滾動、使用z-index進行z軸的排序等。所以,為了實現這些效果,渲染引擎還需要為特定的節點生成專用的圖層,並生成一棵對應的圖層樹。

那什麼是圖層呢?相信用過Photoshop的小夥伴對圖層並不陌生。我們也可以在Chrome瀏覽器的開發者工具中,選擇Layers標籤(如果沒有,可以在更多工具中查詢),就可以看到頁面的分層情況,以掘金首頁為例,其分層情況如下:

畫了20張圖,詳解瀏覽器渲染引擎工作原理

可以看到,渲染引擎給頁面分了很多圖層,這些圖層會按照一定順序疊加在一起,就形成了最終的頁面。這裡,將頁面分解成多個圖層的操作就成為分層, 最後將這些圖層合併到一層的操作就成為合成, 分層和合成通常是一起使用的。Chrome 引入了分層和合成的機制就是為了提升每幀的渲染效率。

通常情況下,並不是渲染樹上的每個節點都包含一個圖層,如果一個節點沒有對應的圖層,那這個節點就會屬於其父節點的圖層。那什麼樣的節點才能讓瀏覽器引擎為其建立一個新的圖層呢?需要滿足以下其中一個條件:

(1)擁有層疊上下文屬性的元素

我們看到的頁面通常是二維的平面,而層疊上下文能夠讓頁面具有三維的概念。這些 HTML 元素按照自身屬性的優先順序分佈在垂直於這個二維平面的 z 軸上。下面是盒模型的層疊規則:

畫了20張圖,詳解瀏覽器渲染引擎工作原理

對於上圖,由上到下分別是:

  • 背景和邊框:建立當前層疊上下文元素的背景和邊框。
  • 負的z-index:當前層疊上下文中,z-index屬性值為負的元素。
  • 塊級盒:文件流內非行內級非定位後代元素。
  • 浮動盒:非定位浮動元素。
  • 行內盒:文件流內行內級非定位後代元素。
  • z-index:0:層疊級數為0的定位元素。
  • 正z-index:z-index屬性值為正的定位元素。

注意: 當定位元素z-index:auto,生成盒在當前層疊上下文中的層級為 0,不會建立新的層疊上下文,除非是根元素。

(2)需要裁剪的元素

什麼是裁剪呢?假如有一個固定寬高的div盒子,而裡面的文字較多超過了盒子的高度,這時就會產生裁剪,瀏覽器渲染引擎會把裁剪文字內容的一部分用於顯示在 div 區域。當出現裁剪時,瀏覽器的渲染引擎就會為文字部分單獨建立一個圖層,如果出現捲軸,那麼捲軸也會被提升為單獨的圖層。

2. 繪製圖層

在完成圖層樹的構建之後,渲染引擎會對圖層樹中的每個圖層進行繪製,下面就來看看渲染引擎是怎麼實現圖層繪製的。

渲染引擎在繪製圖層時,會把一個圖層的繪製分成很多繪製指令,然後把這些指令按照順序組成一個待繪製的列表:

畫了20張圖,詳解瀏覽器渲染引擎工作原理

可以看到,繪製列表中的指令就是一系列的繪製操作。通常情況下,繪製一個元素需要執行多條繪製指令,因為每個元素的背景、邊框等屬性都需要單獨的指令進行繪製。所以在圖層繪製階段,輸出的內容就是繪製列表。

在Chrome瀏覽器的開發者工具中,透過Layer標籤可以看到圖層的繪製列表和繪製過程:

繪製列表只是用來記錄繪製順序和繪製指令的列表,而繪製操作是由渲染引擎中的合成執行緒來完成的。當圖層繪製列表準備好之後,主執行緒會把該繪製列表提交給合成執行緒。

注意:合成操作是在合成執行緒上完成的,所以,在執行合成操作時並不會影響到主執行緒的執行。 ​

很多情況下,圖層可能很大,比如掘金的一篇長文,需要滾動很久才能到底,但是使用者只能看到視口的內容,所以沒必要把整個圖層都繪製出來。因此,合成執行緒會將圖層劃分為圖塊,這些圖塊的大小通常是 256x256 或者 512x512。合成執行緒會優先將視口附近的圖塊生成點陣圖。實際生成點陣圖的操作是在光柵化階段來執行的,所謂的光柵化就是按照繪製列表中的指令生成圖片。

當所有的圖塊都被光柵化之後,合成執行緒就會生成一個繪製圖塊的命令,瀏覽器相關程序收到這個指令之後,就會將其頁面內容繪製在記憶體中,最後將記憶體顯示在螢幕上,這樣就完成了頁面的繪製。

至此,整個渲染流程就完成了,其過程總結如下:

  1. 將HTML內容構建成DOM樹;
  2. 將CSS內容構建成CSSOM樹;
  3. 將DOM 樹和 CSSOM 樹合成渲染樹;
  4. 根據渲染樹進行頁面元素的佈局;
  5. 對渲染樹進行分層操作,並生成分層樹;
  6. 為每個圖層生成繪製列表,並提交到合成執行緒;
  7. 合成執行緒將圖層分成不同的圖塊,並透過柵格化將圖塊轉化為點陣圖;
  8. 合成執行緒給瀏覽器程序傳送繪製圖塊指令;
  9. 瀏覽器程序會生成頁面,並顯示在螢幕上。

六、其他

1. 重排和重繪

說完瀏覽器引擎的渲染流程,再來看兩個重要的概念:重排(Reflow)和重繪(Repaint)。 ​

我們知道,渲染樹是動態構建的,所以,DOM節點和CSS節點的改動都可能會造成渲染樹的重新構建。渲染樹的改動就會造成頁面的重排或者重繪。下面就來看看這兩個概念,以及它們觸發的條件和減少觸發的操作。

(1)重排

當我們的操作引發了 DOM 樹中幾何尺寸的變化(改變元素的大小、位置、佈局方式等),這時渲染樹裡有改動的節點和它影響的節點都要重新計算。這個過程就叫做重排,也稱為迴流。在改動發生時,要重新經歷頁面渲染的整個流程,所以開銷是很大的。 ​

以下操作都會導致頁面重排:

  • 頁面首次渲染;
  • 瀏覽器視窗大小發生變化;
  • 元素的內容發生變化;
  • 元素的尺寸或者位置發生變化;
  • 元素的字型大小發生變化;
  • 啟用CSS偽類;
  • 查詢某些屬性或者呼叫某些方法;
  • 新增或者刪除可見的DOM元素。

在觸發重排時,由於瀏覽器渲染頁面是基於流式佈局的,所以當觸發迴流時,會導致周圍的DOM元素重新排列,它的影響範圍有兩種:

  • 全域性範圍:從根節點開始,對整個渲染樹進行重新佈局;
  • 區域性範圍:對渲染樹的某部分或者一個渲染物件進行重新佈局。

(2)重繪

當對 DOM 的修改導致了樣式的變化、但未影響其幾何屬性(比如修改顏色、背景色)時,瀏覽器不需重新計算元素的幾何屬性、直接為該元素繪製新的樣式(會跳過重排環節),這個過程叫做重繪。簡單來說,重繪是由對元素繪製屬性的修改引發的。 ​

當我們修改元素繪製屬性時,頁面佈局階段不會執行,因為並沒有引起幾何位置的變換,所以就直接進入了繪製階段,然後執行之後的一系列子階段。相較於重排操作,重繪省去了佈局和分層階段,所以執行效率會比重排操作要高一些。

下面這些屬性會導致迴流:

  • color、background 相關屬性:background-color、background-image 等;
  • outline 相關屬性:outline-color、outline-width 、text-decoration;
  • border-radius、visibility、box-shadow。

注意: 當觸發重排時,一定會觸發重繪,但是重繪不一定會引發重排。

相對來說,重排操作的消耗會比較大,所以在操作中儘量少的造成頁面的重排。為了減少重排,可以透過以下方式進行最佳化:

  • 在條件允許的情況下儘量使用 CSS3 動畫,它可以呼叫 GPU 執行渲染。
  • 操作DOM時,儘量在低層級的DOM節點進行操作
  • 不要使用table佈局, 一個小的改動可能會使整個table進行重新佈局
  • 使用CSS的表示式
  • 不要頻繁操作元素的樣式,對於靜態頁面,可以修改類名,而不是樣式。
  • 使用absolute或者fixed,使元素脫離文件流,這樣他們發生變化就不會影響其他元素
  • 避免頻繁操作DOM,可以建立一個文件片段documentFragment,在它上面應用所有DOM操作,最後再把它新增到文件中
  • 將元素先設定display: none,操作結束後再把它顯示出來。因為在display屬性為none的元素上進行的DOM操作不會引發迴流和重繪。
  • 將DOM的多個讀操作(或者寫操作)放在一起,而不是讀寫操作穿插著寫。這得益於瀏覽器的渲染佇列機制

瀏覽器針對頁面的迴流與重繪,進行了自身的最佳化——渲染佇列, 瀏覽器會將所有的迴流、重繪的操作放在一個佇列中,當佇列中的操作到了一定的數量或者到了一定的時間間隔,瀏覽器就會對佇列進行批處理。這樣就會讓多次的迴流、重繪變成一次迴流重繪。

2. JavaScript對DOM的影響

最後我們再看看看JavaScript指令碼對DOM的影響。當解析器解析HTML時,如果遇到了

來看一段程式碼:

<html>
    <body>
        <div>hello juejin</div>
        <script>
            document.getElementsByTagName('div')[0].innerText = 'juejin yyds'
        </script>
        <p>hello world</p>
    </body>
</html>
複製程式碼

這裡,當解析完div標籤後,就會解析script標籤,這時的DOM結構如下:

畫了20張圖,詳解瀏覽器渲染引擎工作原理

這時,HTML解析器就會暫停工作,JavaScript引擎就會開始工作,並執行script標籤中的指令碼內容。由於這段指令碼修改了第一個div的內容,所以執行完這個指令碼之後,div中的文字就變成了“juejin yyds”,當指令碼執行完成之後,HTML解析器就會恢復解析過程,繼續解析後面的內容,直至生成最終的DOM。

上面我們說的JavaScript指令碼是透過script標籤直接嵌入到HTML中的。當在頁面中引入JavaScript指令碼時,情況就會變得複雜。比如:

<html>
    <body>
        <div>hello juejin</div>
        <script type="text/javascript" src='./index.js'></script>
        <p>hello world</p>
    </body>
</html>
複製程式碼

其實這裡的執行流程和上面時一樣的,當遇到script標籤時,HTML解析器都會暫停解析並去執行指令碼檔案。不過這裡執行 JavaScript 指令碼時,需要先下載指令碼。指令碼的下載過程會阻塞 DOM 的解析,而通常下載又是非常耗時的,會受到網路環境、JavaScript 指令碼檔案大小等因素的影響。 ​

經過上面的分析可知,JavaScript 執行緒會阻塞 DOM 的解析,我們可以透過CDN、壓縮指令碼等方式來加速 JavaScript 指令碼的載入。如果指令碼檔案中沒有操作DOM的相關程式碼,就可以將JavaScript指令碼設定為非同步載入,可以給script標籤新增 async 或 defer 屬性來實現指令碼的非同步載入。兩者的使用方式如下:

<script async type="text/javascript" src='./index.js'></script>
<script defer type="text/javascript" src='./index.js'></script>
複製程式碼

下圖可以直觀的看出非同步載入和直接載入的區別:

畫了20張圖,詳解瀏覽器渲染引擎工作原理

其中藍色代表JavaScript指令碼載入時間,紅色代表JavaScript指令碼執行時間,綠色代表HTML解析。

defer 和 async屬性都是去非同步載入外部的JS指令碼檔案,它們都不會阻塞頁面的解析,其區別如下:

  • 執行順序: 多個帶async屬性的標籤,不能保證載入的順序;多個帶defer屬性的標籤,按照載入順序執行;
  • 指令碼是否並行執行: async屬性,表示後續文件的載入和執行與js指令碼的載入和執行是並行進行的,即非同步執行;defer屬性,載入後續文件的過程和js指令碼的載入(此時僅載入不執行)是並行進行的(非同步),JavaScript 指令碼需要等到文件所有元素解析完成之後才執行,DOMContentLoaded事件觸發執行之前。

再來看另外一種情況,示例程式碼如下:

<html>
    <head>
    <style src='./style.css'></style>
    </head>
    <body>
        <div>hello juejin</div>
        <script>
            const ele = document.getElementsByTagName('div')[0];
            ele.innerText = 'juejin yyds';    // 操作DOM
            ele.style.color = 'skyblue';      // 操作CSSOM
        </script>
        <p>hello world</p>
    </body>
</html>
複製程式碼

上面的程式碼中,第9行是操作DOM的,而第10行是操作CSSOM的,所以在執行 JavaScript 指令碼之前,還需要先解析 JavaScript 語句之上所有的 CSS 樣式。所以如果程式碼裡引用了外部的 CSS 檔案,那麼在執行 JavaScript 之前,還需要 等待外部的 CSS 檔案下載完成,並解析生成 CSSOM 物件之後,才能執行 JavaScript 指令碼。而 JavaScript 引擎在解析 JavaScript 之前,是不知道 JavaScript 是否操縱了 CSSOM 的,所以渲染引擎在遇到 JavaScript 指令碼時,不管該指令碼是否操縱了 CSSOM,都會執行 CSS 檔案下載,解析操作,再執行 JavaScript 指令碼。

所以,JavaScript 會阻塞 DOM 生成,而樣式檔案又會阻塞 JavaScript 的執行,我們在開發時需要格外注意這一點。

最後再來看一種情況,示例程式碼如下:

<html>
    <head>
        <style src='./style.css'></style>
    </head>
    <body>
        <div>hello juejin</div>
        <script type="text/javascript" src='./index.js'></script>
        <p>hello world</p>
    </body>
</html>
複製程式碼

這段HTML程式碼中包含了CSS外部引用和JavaScript外部檔案,在接收到 HTML 資料之後的預解析過程中,HTML 預解析器識別出來了有 CSS 檔案和 JavaScript 檔案需要下載,就會同時發起兩個檔案的下載請求。

總結

如果你覺得此文對你有一丁點幫助,點個贊。或者可以加入我的開發交流群:1025263163相互學習,我們會有專業的技術答疑解惑

如果你覺得這篇文章對你有點用的話,麻煩請給我們的開源專案點點star: http://github.crmeb.net/u/defu 不勝感激 !

分類: 新聞
時間: 2021-10-12

相關文章

米芾書法《天馬賦》被康熙譽為“前無古人”,代表性20字筆法詳解

米芾書法《天馬賦》被康熙譽為“前無古人”,代表性20字筆法詳解
米芾<天馬賦>行書縱橫捭闔.筆走龍蛇,有氣吞萬里如虎之勢,被康熙譽為"前無古人",下面我對米芾行書<天馬賦>中的一些字進行詳細解讀,對於初學者深入掌握這20 ...

空氣開關跟空氣什麼關係?詳解空氣開關結構與原理

空氣開關跟空氣什麼關係?詳解空氣開關結構與原理
在家庭電路系統中,有一個非常重要的部件,那就是空氣開關.空氣開關也稱空氣斷路器,是保護家庭電路安全非常重要的一個部件.在電路發生過載.短路時,空氣開關可以有效的保護線路和電源.那麼它為什麼被稱為空氣開 ...

共生菌如何PK致病菌?5文詳解&quot;腸道里的你死我活&quot;
10 月 13 日的<熱心腸日報>,我們解讀了 9 篇文獻,關注:定植抵抗,共生菌-致病菌互作,免疫性糖尿病,腸黏膜損傷,阿爾茨海默病,維生素B12. ​​ Cell子刊:大腸桿菌能否抵抗 ...

早餐店的手抓餅為什麼好吃?40年老阿姨教你做,詳解秘訣技巧

早餐店的手抓餅為什麼好吃?40年老阿姨教你做,詳解秘訣技巧
大家好我是伊華,今天分享一款,早餐店賣得非常火的手抓餅,絲絲縷縷的小薄層,口感鹹香,柔軟勁道,涼了也不會硬,教程毫無保留,全程詳細講解 所用食材: 鹽5克 普通麵粉500克 滾燙的開水200克 涼白開 ...

「大學詳解」蘭州大學|價效比最高的985高校

「大學詳解」蘭州大學|價效比最高的985高校
知識點一: 在陝西, 蘭州大學的分數線在一眾985院校中,一般排名倒數, 除了極個別學校的分數線,在一些年份大起大落(如今年在陝招生的中山大學和東南大學) 蘭大在陝西的分數線,只比西北農林科技大學高. ...

1200AH鋰電800W太陽能,配置到頭!H500輕卡房車“網紅1號”詳解

1200AH鋰電800W太陽能,配置到頭!H500輕卡房車“網紅1號”詳解
藍牌C本即可駕駛的輕卡房車,可能是當下最熱門的車型之一.而這種型別的產品市面並不多!最起碼沒到依維柯或者上汽大通底盤那般千篇一律,所以這類輕卡底盤房車每次有新品推出,都引得老車友矚目. 今天要為大家介 ...

新農合2021異地報銷政策,詳解跨地區就醫報銷比例變化

新農合2021異地報銷政策,詳解跨地區就醫報銷比例變化
新農合,也叫做新型農村合作醫療,目前新農合和城鎮居民醫保,統一合併為城鄉居民醫保,很多農民朋友因為生病在老家得不到有效治療,需要轉移到省城治療,甚至跨省求醫,也有在外地打工.探親.旅行的兄弟在外省生病 ...

蘋果真正的大殺器,是你不曾注意到的這張圖丨週末副刊vol.14

蘋果真正的大殺器,是你不曾注意到的這張圖丨週末副刊vol.14
本週三,被譽為"科技春晚"的蘋果秋季新品釋出會如期舉行.週五晚國行版iPhone 13預售秒空的成績,讓它成為比去年火爆的iPhone 12更受歡迎的一款產品. 國內外果粉都不約而 ...

教你在家輕鬆蒸饅頭花捲,詳解4種花卷手法,做法簡單,一學就會

教你在家輕鬆蒸饅頭花捲,詳解4種花卷手法,做法簡單,一學就會
教你在家輕鬆蒸饅頭花捲,詳解4種花卷手法,做法簡單,一學就會.作為北方人,一般都離不開面食,饅頭花捲烙餅和麵條,幾乎家家戶戶都離不開.很多人對於蒸饅頭或者包子花捲之類的發酵麵食或多或少都會碰到一些問題 ...

易建聯與杜蘭特的天賦差距多大?看完這五張圖,一目瞭然

易建聯與杜蘭特的天賦差距多大?看完這五張圖,一目瞭然
自從姚明退役之後,易建聯成為了中國男籃的一哥.2007年易建聯參加了NBA選秀被雄鹿隊選中,雖然沒有像姚明那樣打出令人驚豔的表現成為NBA巨星,但是易建聯的身體天賦毋庸置疑,無論是運動能力還是投射能力 ...

“北方大學”vs南方大學區別多大?4張圖有趣的再現,差別明顯

“北方大學”vs南方大學區別多大?4張圖有趣的再現,差別明顯
文/艾小貝愛教育 "北方大學"和"南方大學"的區別有多大?還是挺明顯的. 報考大學對於學生是一個走出去,增加自身經歷的最好機會,所以位置的選擇就會變得很重要了, ...

一圖一解,魯班獎大量例項機電安裝細部做法指導,一學就會

一圖一解,魯班獎大量例項機電安裝細部做法指導,一學就會
一圖一解,魯班獎大量例項機電安裝細部做法指導,一學就會 機電安裝工程在整個工程建築施工中是個很重要的環節,涉及面廣,施工技術含量高,它直接影響到建築工程質量.每個施工人員需要具備一定的工作經驗和能力, ...

仰臥起坐正確版姿勢詳解

仰臥起坐正確版姿勢詳解
晚安[仰臥起坐正確版姿勢詳解 ]我們常用的雙手抱頭做仰臥起坐,存在兩大缺陷:對頸椎不友好.對腰肌不友好.正確的仰臥起坐要記住2個要點:雙手別抱頭,腰部儘量別離開地面.詳解頭部:身體仰臥在墊上,頭部與軀 ...

工程人的福利:超全工程施工全過程詳解教程,唯獨一份一週後刪除

工程人的福利:超全工程施工全過程詳解教程,唯獨一份一週後刪除
工程人的福利來了!超全的工程施工全過程詳解發布,唯獨一份一週後刪除,還不快來領取! 作為一個工程人,咱們首先要做的就是詳細瞭解工程施工的全過程,但是會有很多工程人和小白們對工程施工全過程都是一知半解的 ...

你身邊有沒有小人?你會辨別哪些是小人嗎?八張圖揭示小人的嘴臉

你身邊有沒有小人?你會辨別哪些是小人嗎?八張圖揭示小人的嘴臉
行走在社會上,總會遇到形形色色的人.有的人是真誠善良的,可以結交的.而有的人則可能是陽奉陰違的,需要你擦亮眼睛去辨別,然後敬而遠之的.否則,不知道哪一天你就可能被賣了.我們在生活和工作中難免會遇到小人 ...

初代CS:你的青春在哪張圖裡

初代CS:你的青春在哪張圖裡
記得之前在哪看到過一段話,說:"當沙城裡變得空無一人:當白房的後門不再響起--逝去的CS,其實也是一代人.一代青春的離去--"每每想起,不禁讓人黯然神傷.記得剛接觸CS時,還是初三 ...

清華才女:初中數學就“死磕”這12張圖,3年考試從未下過140+

清華才女:初中數學就“死磕”這12張圖,3年考試從未下過140+
很多同學覺得數學難,無非就是公式難背,題目難以理解,很難以最快的方式得出答案. 而數學作為基礎性知識的學科,基礎好壞也決定了很多科目的學習.雖然以後的發展是不分文理科目,但數學更多的是學一種理性的思維 ...

初中數學:教師總結了三年定理公式,19張圖全搞定

初中數學:教師總結了三年定理公式,19張圖全搞定
初中數學:教師總結了三年定理公式,19張圖全搞定! 更多初中.高中知識,可以在大師一百app中檢視,獲取哦- 數學大師