“
關于更多:詳見這篇文章[2]
”
URL 重寫
然后會查看 URL 重寫規則,如果請求的文件是真實存在的,比如圖片、html、css、js文件等,則會直接把這個文件返回。
否則服務器會按照規則把請求重寫到 一個 REST 風格的 URL 上。
然后根據動態語言的腳本,來決定調用什么類型的動態文件解釋器來處理這個請求。
以 PHP 語言的 MVC 框架舉例,它首先會初始化一些環境的參數,根據 URL 由上到下地去匹配路由微信瀏覽器內核是什么,然后讓路由所定義的方法去處理請求。
五、瀏覽器接受響應
瀏覽器接收到來自服務器的響應資源后,會對資源進行分析。
首先查看 ,根據不同狀態碼做不同的事(比如上面提到的重定向)。
如果響應資源進行了壓縮(比如 gzip),還需要進行解壓。
然后,對響應資源做緩存。
接下來,根據響應資源里的MIME[3]類型去解析響應內容(比如 HTML、Image各有不同的解析方式)。
六、渲染頁面
瀏覽器內核
不同的瀏覽器內核,渲染過程也不完全相同,但大致流程都差不多。
基本流程
6.1. HTML 解析
首先要知道瀏覽器解析是從上往下一行一行地解析的。
解析的過程可以分為四個步驟:
① 解碼()
傳輸回來的其實都是一些二進制字節數據,瀏覽器需要根據文件指定編碼(例如UTF-8)轉換成字符串,也就是HTML 代碼。
② 預解析(pre-)
預解析做的事情是提前加載資源,減少處理時間,它會識別一些會請求資源的屬性,比如img標簽的src屬性,并將這個請求加到請求隊列中。
③ 符號化()
符號化是詞法分析的過程,將輸入解析成符號,HTML 符號包括,開始標簽、結束標簽、屬性名和屬性值。
它通過一個狀態機去識別符號的狀態,比如遇到狀態都會產生變化。
④ 構建樹(tree )
“
注意:符號化和構建樹是并行操作的,也就是說只要解析到一個開始標簽,就會創建一個 DOM 節點。
”
在上一步符號化中,解析器獲得這些標記,然后以合適的方法創建DOM對象并把這些符號插入到DOM對象中。
Web page parsing
Web page parsing
This is an example Web page.
瀏覽器容錯進制
你從來沒有在瀏覽器看過類似”語法無效”的錯誤,這是因為瀏覽器去糾正錯誤的語法,然后繼續工作。
事件
當整個解析的過程完成以后,瀏覽器會通過事件來通知DOM解析完成。
6.2. CSS 解析
一旦瀏覽器下載了 CSS,CSS 解析器就會處理它遇到的任何 CSS,根據語法規范[4]解析出所有的 CSS 并進行標記化,然后我們得到一個規則表。
CSS 匹配規則
在匹配一個節點對應的 CSS 規則時,是按照從右到左的順序的,例如:div p { font-size :14px }會先尋找所有的p標簽然后判斷它的父元素是否為div。
所以我們寫 CSS 時,盡量用 id 和 class,千萬不要過度層疊。
6.3. 渲染樹
其實這就是一個 DOM 樹和 CSS 規則樹合并的過程。
“
注意:渲染樹會忽略那些不需要渲染的節點,比如設置了:none的節點。
”
計算
通過計算讓任何尺寸值都減少到三個可能之一:auto、百分比、px,比如把rem轉化為px。
級聯
瀏覽器需要一種方法來確定哪些樣式才真正需要應用到對應元素,所以它使用一個叫做的公式微信瀏覽器內核是什么,這個公式會通過:
標簽名、class、id
是否內聯樣式
!
然后得出一個權重值,取最高的那個。
渲染阻塞
當遇到一個標簽時,DOM 構建會被暫停,直至腳本完成執行,然后繼續構建 DOM 樹。
但如果 JS 依賴 CSS 樣式,而它還沒有被下載和構建時,瀏覽器就會延遲腳本執行,直至 CSS Rules 被構建。
所有我們知道:
為了避免這種情況,應該以下原則:
另外,如果要改變阻塞模式,可以使用 defer 與 async,詳見:這篇文章[5]
6.4. 布局與繪制
確定渲染樹種所有節點的幾何屬性,比如:位置、大小等等,最后輸入一個盒子模型,它能精準地捕獲到每個元素在屏幕內的準確位置與大小。
然后遍歷渲染樹,調用渲染器的 paint() 方法在屏幕上顯示其內容。
6.5. 合并渲染層
把以上繪制的所有圖片合并,最終輸出一張圖片。
6.6. 回流與重繪
回流()
當瀏覽器發現某個部分發現變化影響了布局時,需要倒回去重新渲染,會從html標簽開始遞歸往下,重新計算位置和大小。
基本是無法避免的,因為當你滑動一下鼠標、 窗口,頁面就會產生變化。
重繪()
改變了某個元素的背景色、文字顏色等等不會影響周圍元素的位置變化時,就會發生重繪。
每次重繪后,瀏覽器還需要合并渲染層并輸出到屏幕上。
回流的成本要比重繪高很多,所以我們應該盡量避免產生回流。
比如:
6.7. 編譯執行
大致流程
可以分為三個階段:
1. 詞法分析
JS 腳本加載完畢后,會首先進入語法分析階段,它首先會分析代碼塊的語法是否正確,不正確則拋出“語法錯誤”,停止執行。
幾個步驟:
2. 預編譯
JS 有三種運行環境:
每進入一個不同的運行環境都會創建一個對應的執行上下文,根據不同的上下文環境,形成一個函數調用棧,棧底永遠是全局執行上下文,棧頂則永遠是當前執行上下文。
創建執行上下文
創建執行上下文的過程中,主要做了以下三件事:
建立作用域鏈
確定 This 指向
3. 執行
JS 線程
雖然 JS 是單線程的,但實際上參與工作的線程一共有四個:
“
其中三個只是協助,只有 JS 引擎線程是真正執行的
”
注:瀏覽器對同一域名的并發連接數是有限的,通常為 6 個。
宏任務
分為:
微任務
微任務是ES6和Node環境下的,主要 API 有:,.。
微任務的執行在宏任務的同步任務之后,在異步任務之前。
代碼例子
console.log('1'); // 宏任務 同步
setTimeout(function() {
console.log('2'); // 宏任務 異步
})
new Promise(function(resolve) {
console.log('3'); // 宏任務 同步
resolve();
}).then(function() {
console.log('4') // 微任務
})
console.log('5') // 宏任務 同步
以上代碼輸出順序為:1,3,5,4,2
參考文檔
[1]
你所不知道的 HSTS:
[2]
詳見這篇文章:
[3]