這篇文章專治面試中:"在瀏覽器輸入URL回車以後發生了什麼?"、"瀏覽器輸入URL發送的一系列操做!"等面試問題。 css
這個問題已是老生常談了,更是常常被做爲面試的壓軸題出現,網上也有不少文章,但最近閒的無聊,而後就本身作了一篇筆記,感受比以前理解更透徹了。html
這篇筆記是我這兩天看了數十篇文章總結出來的,因此相對全面一點,但因爲我是作前端的,因此會比較重點分析瀏覽器渲染頁面那一部分,至於其餘部分我會羅列出關鍵詞,感興趣的能夠自行查閱,前端
本文的步驟是創建在,請求的是一個簡單的 HTTP 請求,沒有 HTTPS、HTTP二、最簡單的 DNS、沒有代理、而且服務器沒有任何問題的基礎上,儘管這是不切實際的。html5
首先判斷你輸入的是一個合法的 URL 仍是一個待搜索的關鍵詞,而且根據你輸入的內容進行自動完成、字符編碼等操做。git
因爲安全隱患,會使用 HSTS 強制客戶端使用 HTTPS 訪問頁面。詳見:你所不知道的 HSTS。github
瀏覽器還會進行一些額外的操做,好比安全檢查、訪問限制(以前國產瀏覽器限制 996.icu)。面試
先檢查瀏覽器中是否有存在緩存,沒有則調用系統庫函數進行查詢。瀏覽器
操做系統也有本身的 DNS緩存,但在這以前,會向檢查域名是否存在本地的 Hosts 文件裏,沒有則向 DNS 服務器發送查詢請求。緩存
路由器也有本身的緩存。安全
ISP DNS(因特網服務提供商) 就是在客戶端電腦上設置的首選 DNS 服務器,它們在大多數狀況下都會有緩存。
在前面全部步驟沒有緩存的狀況下,本地 DNS 服務器會將請求轉發到互聯網上的根域,下面這個圖很好的詮釋了整個流程:
須要注意的點
TCP/IP 分爲四層,在發送數據時,每層都要對數據進行封裝:
在前面的步驟咱們已經獲得服務器的 IP 地址,瀏覽器會開始構造一個 HTTP 報文,其中包括:
其中須要注意的點:
傳輸層會發起一條到達服務器的 TCP 鏈接,爲了方便傳輸,會對數據進行分割(以報文段爲單位),並標記編號,方便服務器接受時可以準確地還原報文信息。
在創建鏈接前,會先進行 TCP 三次握手。關於 TCP/IP 三次握手,網上已經有不少段子和圖片生動地描述了。
將數據段打包,並加入源及目標的IP地址,而且負責尋找傳輸路線。
判斷目標地址是否與當前地址處於同一網絡中,是的話直接根據 Mac 地址發送,不然使用路由表查找下一跳地址,以及使用 ARP 協議查詢它的 Mac 地址。
注意:在 OSI 參考模型中 ARP 協議位於鏈路層,但在 TCP/IP 中,它位於網絡層。
以太網協議
根據以太網協議將數據分爲以「幀」爲單位的數據包,每一幀分爲兩個部分:
Mac 地址
以太網規定了連入網絡的全部設備都必須具有「網卡」接口,數據包都是從一塊網卡傳遞到另外一塊網卡,網卡的地址就是 Mac 地址。每個 Mac 地址都是獨一無二的,具有了一對一的能力。
廣播
發送數據的方法很原始,直接把數據經過 ARP 協議,向本網絡的全部機器發送,接收方根據標頭信息與自身 Mac 地址比較,一致就接受,不然丟棄。
注意: 接收方迴應是單播。
相關知識點: ARP 攻擊
服務器接受請求 接受過程就是把以上步驟逆轉過來,參見上圖。
大體流程
HTTPD
最多見的 HTTPD 有 Linux 上經常使用的 Apache 和 Nginx,以及 Windows 上的 IIS。
它會監聽獲得的請求,而後開啓一個子進程去處理這個請求。
處理請求
接受 TCP 報文後,會對鏈接進行處理,對HTTP協議進行解析(請求方法、域名、路徑等),而且進行一些驗證:
重定向
假如服務器配置了 HTTP 重定向,就會返回一個 301永久重定向響應,瀏覽器就會根據響應,從新發送 HTTP 請求(從新執行上面的過程)。
關於更多:詳見這篇文章
URL 重寫
而後會查看 URL 重寫規則,若是請求的文件是真實存在的,好比圖片、html、css、js文件等,則會直接把這個文件返回。
不然服務器會按照規則把請求重寫到 一個 REST 風格的 URL 上。
而後根據動態語言的腳本,來決定調用什麼類型的動態文件解釋器來處理這個請求。
以 PHP 語言的 MVC 框架舉例,它首先會初始化一些環境的參數,根據 URL 由上到下地去匹配路由,而後讓路由所定義的方法去處理請求。
瀏覽器接收到來自服務器的響應資源後,會對資源進行分析。
首先查看 Response header,根據不一樣狀態碼作不一樣的事(好比上面提到的重定向)。
若是響應資源進行了壓縮(好比 gzip),還須要進行解壓。
而後,對響應資源作緩存。
接下來,根據響應資源裏的 MIME 類型去解析響應內容(好比 HTML、Image各有不一樣的解析方式)。
瀏覽器內核
不一樣的瀏覽器內核,渲染過程也不徹底相同,但大體流程都差很少。
基本流程
首先要知道瀏覽器解析是從上往下一行一行地解析的。
解析的過程能夠分爲四個步驟:
1.解碼(encoding)
傳輸回來的其實都是一些二進制字節數據,瀏覽器須要根據文件指定編碼(例如UTF-8)轉換成字符串,也就是HTML 代碼。
2.預解析(pre-parsing)
預解析作的事情是提早加載資源,減小處理時間,它會識別一些會請求資源的屬性,好比img標籤的src屬性,並將這個請求加到請求隊列中。
3.符號化(Tokenization)
符號化是詞法分析的過程,將輸入解析成符號,HTML 符號包括,開始標籤、結束標籤、屬性名和屬性值。
它經過一個狀態機去識別符號的狀態,好比遇到<,>狀態都會產生變化。
4.構建樹(tree construction)
注意:符號化和構建樹是並行操做的,也就是說只要解析到一個開始標籤,就會建立一個 DOM 節點。
在上一步符號化中,解析器得到這些標記,而後以合適的方法建立DOM對象並把這些符號插入到DOM對象中。
<html>
<head>
<title>Web page parsing</title>
</head>
<body>
<div>
<h1>Web page parsing</h1>
<p>This is an example Web page.</p>
</div>
</body>
</html>
複製代碼
你歷來沒有在瀏覽器看過相似」語法無效」的錯誤,這是由於瀏覽器去糾正錯誤的語法,而後繼續工做。
事件
當整個解析的過程完成之後,瀏覽器會經過DOMContentLoaded事件來通知DOM解析完成。
一旦瀏覽器下載了 CSS,CSS 解析器就會處理它遇到的任何 CSS,根據語法規範解析出全部的 CSS 並進行標記化,而後咱們獲得一個規則表。
CSS 匹配規則
在匹配一個節點對應的 CSS 規則時,是按照從右到左的順序的,例如:div p { font-size :14px }會先尋找全部的p標籤而後判斷它的父元素是否爲div。
因此咱們寫 CSS 時,儘可能用 id 和 class,千萬不要過分層疊。
其實這就是一個 DOM 樹和 CSS 規則樹合併的過程。
注意:渲染樹會忽略那些不須要渲染的節點,好比設置了display:none的節點。
計算
經過計算讓任何尺寸值都減小到三個可能之一:auto、百分比、px,好比把rem轉化爲px。
級聯
瀏覽器須要一種方法來肯定哪些樣式才真正須要應用到對應元素,因此它使用一個叫作specificity的公式,這個公式會經過:
而後得出一個權重值,取最高的那個。
渲染阻塞
當遇到一個script標籤時,DOM 構建會被暫停,直至腳本完成執行,而後繼續構建 DOM 樹。
但若是 JS 依賴 CSS 樣式,而它尚未被下載和構建時,瀏覽器就會延遲腳本執行,直至 CSS Rules 被構建。
全部咱們知道:
爲了不這種狀況,應該如下原則:
另外,若是要改變阻塞模式,可使用 defer 與 async,詳見:這篇文章
肯定渲染樹種全部節點的幾何屬性,好比:位置、大小等等,最後輸入一個盒子模型,它能精準地捕獲到每一個元素在屏幕內的準確位置與大小。
而後遍歷渲染樹,調用渲染器的 paint() 方法在屏幕上顯示其內容。
把以上繪製的全部圖片合併,最終輸出一張圖片。
迴流(reflow)
當瀏覽器發現某個部分發現變化影響了佈局時,須要倒回去從新渲染,會從html標籤開始遞歸往下,從新計算位置和大小。
reflow基本是沒法避免的,由於當你滑動一下鼠標、resize 窗口,頁面就會產生變化。
重繪(repaint)
改變了某個元素的背景色、文字顏色等等不會影響周圍元素的位置變化時,就會發生重繪。
每次重繪後,瀏覽器還須要合併渲染層並輸出到屏幕上。
迴流的成本要比重繪高不少,因此咱們應該儘可能避免產生迴流。
好比:
display:none 會觸發迴流,而 visibility:hidden 只會觸發重繪。
大體流程
能夠分爲三個階段:
JS 腳本加載完畢後,會首先進入語法分析階段,它首先會分析代碼塊的語法是否正確,不正確則拋出「語法錯誤」,中止執行。
幾個步驟:
JS 有三種運行環境:
每進入一個不一樣的運行環境都會建立一個對應的執行上下文,根據不一樣的上下文環境,造成一個函數調用棧,棧底永遠是全局執行上下文,棧頂則永遠是當前執行上下文。
建立執行上下文
建立執行上下文的過程當中,主要作了如下三件事:
JS 線程
雖然 JS 是單線程的,但實際上參與工做的線程一共有四個:
其中三個只是協助,只有 JS 引擎線程是真正執行的
注:瀏覽器對同一域名的併發鏈接數是有限的,一般爲 6 個。
宏任務
分爲:
微任務
微任務是ES6和Node環境下的,主要 API 有:Promise,process.nextTick。
微任務的執行在宏任務的同步任務以後,在異步任務以前。
代碼例子
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
複製代碼
參考文檔
本文做者:4Ark
本文連接: 4ark.me/post/b6c7c0…
版權: 本站文章均採用 CC BY-NC-SA 3.0 CN 許可協議,請勿用於商業,轉載註明出處!