地址欄輸入URL
檢查URL
瀏覽器的瀏覽器進程檢查URL,組裝協(xié)議,構成完整的URL,通過進程間通信IPC把URL請求發(fā)送給網(wǎng)絡進程
發(fā)起http請求
網(wǎng)絡進程接收到URL發(fā)起http請求
網(wǎng)絡進程首先查找本地是否存在緩存
如果沒有緩存就進行網(wǎng)絡請求
網(wǎng)絡請求首先進行DNS解析,DNS查過過程也存在緩存的問題,最終拿到目標服務器的ip
網(wǎng)絡進程通過ip地址和服務器建立tcp鏈接
網(wǎng)絡進程構建請求行,請求頭,請求體,發(fā)起網(wǎng)絡請求
拿到響應后
瀏覽器根據(jù)相應的狀態(tài)嗎判斷是否進行重定向
瀏覽器根據(jù)相應的content-type決定如何顯示相應的內容
如果是text/html,瀏覽器為每個頁面分配一個渲染進程,如果屬于same-site(根域名和協(xié)議完全一致就屬于same-site),就在同一渲染進程進行渲染。
渲染進程準備好之后瀏覽器進程發(fā)出提交文檔指令,這里的文檔指的是響應體數(shù)據(jù),渲染進程收到指令后建立和網(wǎng)絡進程的數(shù)據(jù)管道
等文檔數(shù)據(jù)傳輸完成后,渲染進程返回確認提交的消息給瀏覽器進程
瀏覽器進程收到確認提交消息后,更新瀏覽器的界面,包括前進后退的歷史狀態(tài),安全狀態(tài),地址欄的URL,和正真的web頁面
文檔提交后渲染進程開始頁面解析和子資源加載
渲染流程:
構建 DOM 樹
輸入:HTML 文檔;
處理:HTML 解析器解析;
輸出:DOM 數(shù)據(jù)解構。
樣式計算
輸入:CSS 文本;
處理:屬性值標準化,每個節(jié)點具體樣式(繼承、層疊);
輸出:styleSheets(CSSOM)。
布局(DOM 樹中元素的計劃位置)
DOM & CSSOM 合并成渲染樹;
布局樹(DOM 樹中的可見元素);
布局計算。
分層
特定節(jié)點生成專用圖層,生成一棵圖層樹(層疊上下文、Clip,類似 PhotoShop 里的圖層);
擁有層疊上下文屬性(明確定位屬性、透明屬性、CSS 濾鏡、z-index 等)的元素會創(chuàng)建單獨圖層;
沒有圖層的 DOM 節(jié)點屬于父節(jié)點圖層;
需要剪裁的地方也會創(chuàng)建圖層。
繪制指令
輸入:圖層樹;
渲染引擎對圖層樹中每個圖層進行繪制;
拆分成繪制指令,生成繪制列表,提交到合成線程;
輸出:繪制列表。
分塊
合成線程會將較大、較長的圖層(一屏顯示不完,大部分不在視口內)劃分為圖塊(tile, 256256, 512512)。
光柵化(柵格化)
在光柵化線程池中,將視口附近的圖塊優(yōu)先生成位圖(柵格化執(zhí)行該操作);
快速柵格化:GPU 加速,生成位圖(GPU 進程)。
合成繪制
繪制圖塊命令——DrawQuad,提交給瀏覽器進程;
瀏覽器進程的 viz 組件,根據(jù)DrawQuad命令,繪制在屏幕上。
頁面完成
頁面生成完成后,渲染進程發(fā)送消息給瀏覽器進程,收到后會停止標簽欄上圖標的加載動畫
over
優(yōu)化
渲染進程的主線程按照順序首先構建dom樹,樣式計算,布局,繪制
如果發(fā)生重排(比如改變元素大小等)會直接從樣式計算開始從新執(zhí)行,如果發(fā)生重繪(例如改變顏色)會直接從繪制階段開始從新執(zhí)行,所以頻繁的占用渲染進程的主進程,就會產生較大的開銷。所以在性能優(yōu)化中,減少重排和重繪的屬性,渲染引擎將跳過布局和繪制,只執(zhí)行非主線程的合成,例如:CSS的transform就避開重排和重繪,直接在非主線程上執(zhí)行合成操作,沒有占用主線程資源,所以效率最高
HTML渲染流程
在上圖中一并畫了出來,需要經過以下幾個階段:構建 DOM 樹--->樣式計算--->布局--->分層--->繪制--->分塊--->光柵化--->合成
DNS
DNS 的解析是一個遞歸流程,順序如下圖中數(shù)字標記所示:
- 根 DNS 服務器:返回頂級域 DNS 服務器的 IP 地址。
扯一句:世界上有13臺根服務器,而有N臺根鏡像服務器,所以有些國外地址會被墻 ~~~,說明域名解析第一步并不是走的根服務器,而是由根鏡像服務器控制住做了過濾,有選擇性地進行了域名解析,所以如果根域名服務器出了問題,我覺得我們國內仍然可以做到正常訪問,當然只是新更新的域名無法訪問。 - 頂級 DNS 服務器:返回權威 DNS 服務器的 IP 地址
- 權威 DNS 服務器:返回相應主機的 IP 地址
JS 棧 垃圾數(shù)據(jù)回收
- 示例代碼1:
function hello () {
let name = '數(shù)據(jù)'
}
hello()
console.log(name) // Uncaught ReferenceError: name is not defined 在執(zhí)行上下文時,存放在棧內存中的name地址和數(shù)據(jù)【內存塊】已經回收掉了,所以在全局上下文中是訪問不到。
- 示例代碼2:
function hello () {
let name = '前端食堂'
let food = { name: '回鍋肉' }
function world () {
var description = { slogan: '吃好每一頓飯' }
}
world()
}
hello()
上面的代碼所對應的內存堆??臻g如下圖所示:
棧中的垃圾回收比較簡單,當一個函數(shù)執(zhí)行結束后,JavaScript 引擎會通過向下移動 ESP 來銷毀函數(shù)調用棧中所保存的執(zhí)行上下文,ESP 就是記錄當前執(zhí)行狀態(tài)的指針。
- 垃圾回收操作會暫停 JavaScript 的運行,回收完畢后才會恢復執(zhí)行,這種行為就是全停頓。
為了降低全停頓所帶來的卡頓,V8 引擎采用了增量標記(Incremental Marking) 算法進行優(yōu)化,將標記過程分為一個個小任務,這些小任務的執(zhí)行時間比較短,可以穿插在其他的 JavaScript 任務中間執(zhí)行,這樣就不會有明顯的卡頓了。
當然,V8 所采用的優(yōu)化方案不只這一種,而是多種方案綜合使用的,除了增量回收還有并行回收、并發(fā)回收等。
并行回收:垃圾回收器會使用多個輔助線程來并行執(zhí)行垃圾回收
并發(fā)回收:回收線程在執(zhí)行 JavaScript 的過程中,輔助線程在后臺執(zhí)行垃圾回收
如果你了解【** React 的 Concurrent 模式中時間切片的原理 **】,它的實現(xiàn)思想是不是與增量標記算法有異曲同工之妙呢。
其中在頁面渲染以及網(wǎng)絡請求響應的性能優(yōu)化方面可以做的優(yōu)化工作有:
1.DNS優(yōu)化
2.將js寫在body尾部,使不阻塞渲染頁面
3.預加載或懶加載
4.資源壓縮,減少請求次數(shù)