小汪最近在看【WebKit 技術內幕】一書,說實話,這本書寫的太官方了,不通俗易懂。html
可是看完書,對瀏覽器內核的 WebKit 有了進一步的瞭解,因此從瀏覽器內核出發,寫這篇文章以記錄學到的知識,以加深對 WebKit 的理解。前端
相信不少開發人員在面試時都遇到這個問題,這道題可說是很是很是難的,由於深度能夠很是深,廣度能夠很是廣。這題是很是能考查一個前端開發人員的知識體系的題目。java
寫這篇文章的時候,邊寫邊以爲難 !!!git
前端硬核面試專題的完整版在此:前端硬核面試專題,包含:HTML + CSS + JS + ES6 + Webpack + Vue + React + Node + HTTPS + 數據結構與算法 + Git 。github
當你這樣子回答的時候:面試
用戶輸入 url 地址,瀏覽器查詢 DNS 查找對應的請求 IP 地址算法
創建 TCP 鏈接chrome
瀏覽器向服務器發送 http 請求,若是服務器段返回以 301 之類的重定向,瀏覽器根據相應頭中的 location 再次發送請求瀏覽器
服務器端接受請求,處理請求生成 html 代碼,返回給瀏覽器,這時的 html 頁面代碼多是通過壓縮的緩存
瀏覽器接收服務器響應結果,若是有壓縮則首先進行解壓處理,緊接着就是頁面解析渲染
解析該過程分爲:解析 HTML,構建 DOM 樹,DOM 樹與 CSS 樣式進行附着構造呈現樹,佈局、繪製
雖然這大體的過程是對的,但回答不上細節 !深度不夠!!!
面試官給你的臉色是:「很遺憾,這不是咱們要的回答 ! 」
下面讓咱們扒下各個過程細節的外衣,坦誠相見吧 !
瀏覽器引入了 DNS 預取技術。它是利用現有的 DNS 機制,提早解析網頁中可能的網絡鏈接。
當咱們開始在瀏覽器中輸入網址的時候,瀏覽器其實就已經在智能的匹配可能得 url 了。它會從歷史記錄,書籤等地方,找到已經輸入的字符串可能對應的 url ,找到同輸入的地址很匹配的項,而後給出智能提示,讓你能夠補全 url 地址。用戶尚未按下 enter 鍵, 瀏覽器已經開始使用 DNS 預取技術解析該域名了。
對於 chrome 的瀏覽器,若是有該域名相關的緩存,它會直接從緩存中把網頁展現出來,就是說,你尚未按下 enter,頁面就出來了。若是沒有緩存,就仍是會從新請求資源。
假設輸入 www.baidu.com,大概過程:
瀏覽器搜索本身的 DNS 緩存。
在瀏覽器緩存中沒找到,就在操做系統緩存中查找,這一步中也會查找本機的 hosts 看看有沒有對應的域名映射。
在系統中也沒有的話,就到你的路由器來查找,由於路由器通常也會有本身的 DNS 緩存。
若沒有,則操做系統將域名發送至 本地域名服務器——遞歸查詢方式,本地域名服務器 查詢本身的 DNS 緩存,查找成功則返回結果,不然,採用迭代查詢方式。本地域名服務器通常都是你的網絡接入服務器商提供,好比中國電信,中國移動。
本地域名服務器 將獲得的 IP 地址返回給操做系統,同時本身也將 IP 地址緩存起來。
操做系統將 IP 地址返回給瀏覽器,同時本身也將 IP 地址緩存起來,以備下次別的用戶查詢時,能夠直接返回結果,加快網絡訪問。
至此,瀏覽器已經獲得了域名對應的 IP 地址。
參考文章:
blog.csdn.net/wlk20648199… blog.csdn.net/dojiangv/ar…
TCP 是一種面向有鏈接的傳輸層協議。 它能夠保證兩端(發送端和接收端)通訊主機之間的通訊可達。 它可以處理在傳輸過程當中丟包、傳輸順序亂掉等異常狀況;此外它還能有效利用寬帶,緩解網絡擁堵。
三次握手的步驟:(抽象派)
客戶端:hello,你是server麼?
服務端:hello,我是server,你是client麼
客戶端:yes,我是client
複製代碼
在 TCP 鏈接創建完成以後就能夠發送 HTTP 請求了。
而後,待到斷開鏈接時,須要進行四次揮手(由於是全雙工的,因此須要四次揮手)
四次揮手的步驟:(抽象派)
主動方:我已經關閉了向你那邊的主動通道了,只能被動接收了
被動方:收到通道關閉的信息
被動方:那我也告訴你,我這邊向你的主動通道也關閉了
主動方:最後收到數據,以後雙方沒法通訊
複製代碼
在接收和解釋請求消息後,服務器返回一個HTTP響應消息。
HTTP 響應由三個部分組成,分別是:狀態行、消息報頭、響應正文。
狀態代碼:由三位數字組成,第一個數字定義了響應的類別,且有五種可能取值:
常見狀態代碼、狀態描述、說明:
HTTP消息報頭包括:普通報頭、請求報頭、響應報頭、實體報頭。具體不做介紹。
響應正文:就是服務器返回的資源的內容
在瀏覽器沒有完整接受所有HTML文檔時,它就已經開始顯示這個頁面了,不一樣瀏覽器可能解析的過程不太同樣,這裏咱們只介紹 WebKit 的渲染過程。
渲染步驟大體能夠分爲如下幾步:
1. 解析HTML,構建 DOM 樹
2. 解析 CSS ,生成 CSS 規則樹
3. 合併 DOM 樹和 CSS 規則,生成 render 樹
4. 佈局 render 樹( Layout / reflow ),負責各元素尺寸、位置的計算
5. 繪製 render 樹( paint ),繪製頁面像素信息
6. 瀏覽器會將各層的信息發送給 GPU,GPU 會將各層合成( composite ),顯示在屏幕上
複製代碼
其中每一個解釋的過程當中,WebKit 都提供了不少相關的類來一步一步地解釋對應的內部模塊,這裏面不作詳細描述。
下面根據上面的大體過程來一步步細解。
瀏覽器在解析html文件時, 是WebKit 中的 HTML 解釋器的將網絡或者本地磁盤獲取的 HTML 網頁和資源從字節流解釋成 DOM 樹結構。具體過程以下 :
在 WebKit 中這一過程以下:首先是字節流,通過解碼以後是字符流,而後經過詞法分析器會被解釋成詞語(Tokens),以後通過語法分析器構建成節點,最後這些節點被組建成一棵 DOM 樹。
瀏覽器在解析html文件過程當中,會 」自上而下「 加載,並在加載過程當中進行解析渲染。在解析過程當中,若是遇到請求外部資源時,如圖片、外鏈的CSS、iconfont等,請求過程是異步的,並不會影響html文檔進行加載,且統一交由 Browser 進程來處理,這使得資源在不一樣網頁間的共享變得很容易。
HTML 的解釋、佈局和渲染等工做基本上就是工做在渲染線程完成的(這不是絕對的)。由於 DOM 樹只能在渲染線程上建立和訪問,這也就是說構建 DOM 樹的過程只能在渲染線程中進行,可是,從字符到詞語這個階段能夠交給另外的單獨的線程來作。
並且由於有 DNS 預取技術,當用戶正在瀏覽當前網頁的時候,Chromium 提取網頁中的超連接,將域名抽取出來,利用比較少的 CPU 和網絡帶寬來解析這些域名或者 IP 地址,這樣一來,用戶根本感受不到這一過程。當用戶單擊這些連接的時候,能夠節省很多時間,特別在域名解析比較慢的時候,效果特別明顯。
解析過程當中,瀏覽器首先會解析 HTML 文件構建 DOM 樹,而後解析 CSS 文件構建 Render樹,等到 Render 樹構建完成後,瀏覽器開始佈局 Render 樹並將其繪製到屏幕上。
詳情參考小汪以前寫的文章:瀏覽器內核之 HTML 解釋器和 DOM 模型
CSS 解釋過程是指從 CSS 字符串 通過 CSS 解釋器 處理後變成渲染引擎內部規則的表示過程。
生成樣式規則以後,會進行樣式規則匹配,WebKit 會爲其中的一些節點(只限於可視節點)選擇合適的樣式信息,規則的匹配則是由 ElementRuleCollector 類來計算並得到,它根據元素的屬性等,並從 DocumentRuleSets 類中獲取規則集合,依次按照 ID、類別、標籤等選擇器信息逐次匹配得到元素的樣式。
最後,WebKit 對這些規則進行排序。對於該元素須要的樣式屬性,WebKit 選擇從高優先級規則中選取,並將樣式屬性值返回。
從整個網頁的加載和渲染過程來看,CSS 解釋和規則匹配處於 DOM 樹創建以後,RenderObject 樹創建以前,CSS 解釋器解釋後的結果會保存起來,而後 RenderObject 樹基於該結果來進行規範匹配和佈局計算。當網頁有用戶交互或者動畫等動做的時候,經過 CSSDOM 等技術,JavaScript 代碼一樣能夠很是方便地修改 CSS 代碼,WebKit 此時須要從新解釋樣式並重復以上這一過程。
參考小汪以前寫的文章:瀏覽器內核之 CSS 解釋器和樣式佈局
當文檔加載過程當中遇到 js 文件,html 文檔會掛起渲染(加載解析渲染同步)的線程,不只要等待文檔中 js 文件加載完畢,還要等待解析執行完畢,才能夠恢復 html 文檔的渲染線程。由於 JS 有可能會修改 DOM,最爲經典的 document.write,這意味着,在 JS 執行完成前,後續全部資源的下載多是沒有必要的,這是 js 阻塞後續資源下載的根本緣由。因此咱們平時的代碼中,js 是放在 html 文檔末尾的。
並且當遇到執行 JavaScript 代碼的時候,WebKit 先暫停當前 JavaScript 代碼的執行,使用預先掃描器 HTMLPreloadScanner 類來掃描後面的詞語。若是 WebKit 發現它們須要使用其餘資源,那麼使用預資源加載器 HTMLPreloadScanner 類來發送請求,在這以後,才執行 JavaScript 代碼。預先掃描器自己並不建立節點對象,也不會構建 DOM 樹,因此速度比較快。
當 DOM 樹構建完以後,WebKit 觸發 「DOMContentLoaded」 事件,註冊在該事件上的 JavaScript 函數會被調用。當所在資源都被加載完以後,WebKit 觸發 「onload」 事件。
WebKit 將 DOM 樹建立過程當中須要執行的 JavaScript 代碼交由 HTMLScriptRunner 類來負責。工做方式很簡單,就是利用 JavaScript 引擎來執行 Node 節點中包含的代碼。
JS 的解析是由瀏覽器中的 JavaScript 引擎完成的。JS是單線程運行,也就是說,在同一個時間內只能作一件事,全部的任務都須要排隊,前一個任務結束,後一個任務才能開始。可是又存在某些任務比較耗時,如 IO 讀寫等,因此須要一種機制能夠先執行排在後面的任務,這就是:同步任務(synchronous)和異步任務(asynchronous)。
JS 的執行機制就能夠看作是一個主線程加上一個任務隊列(task queue)。同步任務就是放在主線程上執行的任務,異步任務是放在任務隊列中的任務。全部的同步任務在主線程上執行,造成一個執行棧; 異步任務有了運行結果就會在任務隊列中放置一個事件;腳本運行時先依次運行執行棧,而後會從任務隊列裏提取事件,運行任務隊列中的任務,這個過程是不斷重複的,因此又叫作事件循環(Event loop)。
參考小汪以前寫的文章:瀏覽器之 javaScript 引擎
HTML 通過 WebKit 解釋以後,生成 DOM 樹。在 DOM 樹構建完成以後,WebKit 會爲 DOM 樹節點構建 RenderObject 樹,再經過 RenderObject 樹構建出 RenderLayer 樹。
RenderObject 樹是基於 DOM 樹創建起來的一棵新樹,是爲了佈局計算和渲染等機制而構建的一種新的內部表示。RenderObject 樹節點和 DOM 節點不是一一對應關係,由於有可視節點(經常使用的 div img 標籤等)與不可視節點(如 head、meta 標籤),不可視節點是不會構成 RenderObject 樹的。
網頁是有層次結構的,能夠分層的,一是爲了方便設置網頁的層次,二是爲了 WebKit 處理上的便利,爲了簡化渲染的邏輯。
並且 RenderLayer 節點和 RenderObject 節點不是一一對應關係,而是一對多的關係。
當 WebKit 建立 RenderObject 對象以後,每一個對象是不知道本身的位置、大小等信息的,WebKit 根據框模型來計算它們的位置,大小等信息的過程稱爲佈局計算。
佈局計算是一個遞歸的過程,由於一個節點的大小一般須要先計算它的子女節點的位置,大小等信息。
當用戶 網頁的動畫、翻滾網頁、JavaScript 代碼經過 CSSDOM 等操做時還會有從新佈局。
參考小汪以前寫的文章:瀏覽器內核之 CSS 解釋器和樣式佈局
在 WebKit 中,繪圖操做就是繪圖上下文,全部繪圖的操做都是在該上下文中來進行的。
繪圖上下文能夠分紅兩種類型:
一是 2D 圖形上下文(GraphicsContext),用來繪製 2D 圖形的的上下文;
二是 3D 繪圖上下文,是用來繪製 3D 圖形的上下文。
2D 繪圖上下文具體的做用:提供基本繪圖單元的繪製接口以及設置繪圖的樣式。繪圖接口包括畫點,畫線、畫圖片、畫多邊形、畫文字等,繪圖樣式包括顏色、線寬、字號大小、漸變等。
關於 3D 繪圖上下文,它的主要用處是支持 CSS3D、WebGL 等。
網頁的渲染方式,有三種方式,一是軟件渲染,二是硬件加速渲染,三能夠說是混合模式。
若是繪圖操做使用 CPU 來完成,稱之爲軟件繪圖。
若是繪圖操做由 GPU 來完成,稱之爲 GPU 硬件加速繪圖。
理想狀況下,每一個層都有個繪製的存儲區域,這個存儲區域用來保存繪圖的結果。最後,須要將這些層的內容合併到同一個圖像之中,能夠稱之爲合成(Compositing),使用了合成技術的渲染稱之爲合成化渲染。
因此,在完成構建 DOM 樹以後,WebKit 會調用繪圖操做、軟件渲染或者硬件加速渲染或者二者都有,將模型繪製出來,呈如今屏幕上。 至此,瀏覽器渲染完成。
詳情參考小汪以前寫的文章:瀏覽器內核之渲染基礎
如今,當面試官再問你 「從敲入 URL 到瀏覽器渲染完成」 的時候,你的心裏是否是這樣的 ?
你覺得本文就這麼結束了 ? 精彩在後面 !!!
若是以爲本文還不錯,記得給個 star , 你的 star 是我持續更新的動力!