按照渲染的時間順序,流水線可分爲以下幾個子階段:構建 DOM 樹
、樣式計算
、佈局階段
、分層
、柵格化
和顯示
。css
瀏覽器從網絡或硬盤中得到HTML字節數據後會通過一個流程將字節解析爲DOM樹,先將HTML的原始字節數據轉換爲文件指定編碼的字符,而後瀏覽器會根據HTML規範來將字符串轉換成各類令牌標籤,如html、body等。最終解析成一個樹狀的對象模型,就是dom樹;html
獲取css,獲取style標籤內的css、或者內嵌的css,或者當HTML代碼碰見標籤時,瀏覽器會發送請求得到該標籤中標記的CSS,當渲染引擎接收到 CSS 文本時,會執行一個轉換操做,將 CSS 文本轉換爲瀏覽器能夠理解的styleSheets前端
建立佈局樹,遍歷 DOM 樹中的全部可見節點,並把這些節點加到佈局中;而不可見的節點會被佈局樹忽略掉,如 head 標籤下面的所有內容,再好比 body.p.span 這個元素,由於它的屬性包含 dispaly:none,因此這個元素也沒有被包進佈局樹。最後計算 DOM 元素的佈局信息,使其都保存在佈局樹中。佈局完成過程當中,若是有js操做或者其餘操做,對元素的顏色,背景等做出改變就會引發重繪,若是有對元素的大小、定位等有改變則會引發迴流。
git
由於頁面中有不少複雜的效果,如一些複雜的 3D 變換、頁面滾動,或者使用 z-indexing 作 z 軸排序等,爲了更加方便地實現這些效果,渲染引擎還須要爲特定的節點生成專用的圖層,並生成一棵對應的圖層樹。github
渲染引擎實現圖層的繪製,把一個圖層的繪製拆分紅不少小的繪製指令而後再把這些指令按照順序組成一個待繪製列表,當圖層的繪製列表準備好以後,主線程會把該繪製列表提交給合成線程,合成線程會將圖層劃分爲圖塊,而後按照視口附近的圖塊來優先生成位圖(實際生成位圖的操做是由柵格化來執行的。所謂柵格化,是指將圖塊轉換爲位圖)web
一旦全部圖塊都被光柵化,合成線程就會生成一個繪製圖塊的命令,而後將該命令提交給瀏覽器進程,瀏覽器最後進行顯示。瀏覽器
迴流:
當咱們對 DOM 的修改引起了 DOM 幾何尺寸的變化(好比修改元素的寬、高或隱藏元素等)時,瀏覽器須要從新計算元素的幾何屬性(其餘元素的幾何屬性和位置也會所以受到影響),而後再將計算的結果繪製出來。這個過程就是迴流(也叫重排)。緩存
重繪:
當咱們對 DOM 的修改致使了樣式的變化、卻並未影響其幾何屬性(好比修改了顏色或背景色)時,瀏覽器不需從新計算元素的幾何屬性、直接爲該元素繪製新的樣式(跳過了上圖所示的迴流環節)。這個過程叫作重繪。 由此咱們能夠看出,重繪不必定致使迴流,迴流必定會致使重繪。微信
will-change
#divId {
will-change: transform;
}
複製代碼
優勢
網絡
注意:
部分瀏覽器緩存了一個 flush 隊列,把咱們觸發的迴流與重繪任務都塞進去,待到隊列裏的任務多起來、或者達到了必定的時間間隔,或者「不得已」的時候,再將這些任務一口氣出隊。可是當咱們訪問一些即便屬性時,瀏覽器會爲了得到此時此刻的、最準確的屬性值,而提早將 flush 隊列的任務出隊。
層疊上下文
是HTML元素的三維概念,這些HTML元素在一條假想的相對於面向(電腦屏幕的)視窗或者網頁的用戶的z軸上延伸,HTML元素依據其自身屬性按照優先級順序佔用層疊上下文的空間。
擁有層疊上下文屬性:
這裏的剪裁指的是,假如咱們把 div 的大小限定爲 200 * 200 像素,而 div 裏面的文字內容比較多,文字所顯示的區域確定會超出 200 * 200 的面積,這時候就產生了剪裁,渲染引擎會把裁剪文字內容的一部分用於顯示在 div 區域。出現這種裁剪狀況的時候,渲染引擎會爲文字部分單首創建一個層,若是出現滾動條,滾動條也會被提高爲單獨的層。
塊級做用域就是經過詞法環境的棧結構來實現的,而變量提高是經過變量環境來實現,經過這二者的結合,JavaScript 引擎也就同時支持了變量提高和塊級做用域了。
詞法環境跟函數上下文,都是經過棧結構實現的。函數內部經過 var 聲明的變量,在編譯階段全都被存放到變量環境(函數上下文)中,而經過let和const申明的變量會被追加到詞法環境中,當這個塊執行結束以後,追加到詞法做用域的內容又會銷燬掉。
舉個例子:
function foo() {
var test = 1
let myname= 'LuckyWinty'
{
console.log(myname)
let myname= 'winty'
}
console.log(test,'---',myname)
}
foo()
//思考一下會輸出什麼?
複製代碼
執行到第一個console.log
前的執行上下文是這樣的:
從圖中看,第一個console.log
理論上應該輸出 undefined
。可是語法規定了一個"暫時性死區(TDZ,當進入它的做用域,它不能被訪問(獲取或設置)直到執行到達聲明)"
,也就是說雖然經過let聲明的變量已經在詞法環境中了,可是在沒有賦值以前,訪問該變量JavaScript引擎就會拋出一個錯誤。
所以,第一個console.log
會拋錯,[Uncaught ReferenceError: Cannot access 'myname' before initialization]。拋錯則函數會中斷執行,爲了能讓咱們的代碼繼續分析,咱們先加個 try-catch ,而後繼續分析:
function foo() {
var test = 1
let myname= 'LuckyWinty'
try{
{
console.log(myname)
let myname= 'winty'
}
}catch(ex){
console.error(ex)
}
console.log(test,'---',myname)
}
foo()
//思考一下會輸出什麼?
複製代碼
執行到第二個console.log
前的執行上下文是這樣的:
此時,{}
塊做用域中的內容已執行完畢,被銷燬掉了。第二個console.log
會輸出1 "---" "LuckyWinty"
。
在 JavaScript 中,原始類型的賦值會完整複製變量值,而引用類型的賦值是複製引用地址。
在 JavaScript 的執行過程當中, 主要有三種類型內存空間,分別是代碼空間
、棧空間
、堆空間
。 其中的代碼空間主要是存儲可執行代碼的,原始類型(Number、String、Null、Undefined、Boolean、Symbol、BigInt)的數據值都是直接保存在「棧」中的,引用類型(Object)的值是存放在「堆」中的。所以在棧空間中(執行上下文),原始類型存儲的是變量的值,而引用類型存儲的是其在"堆空間"中的地址,當 JavaScript 須要訪問該數據的時候,是經過棧中的引用地址來訪問的,至關於多了一道轉手流程。
在編譯過程當中,若是 JavaScript 引擎判斷到一個閉包,也會在堆空間建立換一個「closure(fn)」
的對象(這是一個內部對象,JavaScript 是沒法訪問的),用來保存閉包中的變量。因此閉包中的變量是存儲在「堆空間」中的。
JavaScript 引擎須要用棧來維護程序執行期間上下文的狀態,若是棧空間大了話,全部的數據都存放在棧空間裏面,那麼會影響到上下文切換的效率,進而又影響到整個程序的執行效率。一般狀況下,棧空間都不會設置太大,主要用來存放一些原始類型的小數據。而引用類型的數據佔用的空間都比較大,因此這一類數據會被存放到堆中,堆空間很大,能存放不少大的數據,不過缺點是分配內存和回收內存都會佔用必定的時間。所以須要「棧」和「堆」兩種空間。