1. 程式人生 > >前端回答從輸入URL到頁面展示都經歷了些什麽

前端回答從輸入URL到頁面展示都經歷了些什麽

自頂向下 ip) 生成 解析器 shee charset 網絡 結構 som

瀏覽器和服務器涉及大量網絡通信內容,此處做了弱化介紹,作為前端主要關註第四部分。
一、 網絡環境保障
我們先假定我們訪問的URL為www.abc.com並且地址不在局域網內;
首先我們所處的局域網的總路由應該和ISP(因特網服務提供商)連接,我們的主機要實現網絡通信必須具備以下四個要素
1、本機的IP地址
2、子網掩碼
3、網關的IP地址(如果我們訪問的網址在局域網內則不需要該項)
4、DNS的IP地址
獲取這四個要素的基本方式有兩種,手動配置(靜態獲取)和通過DHCP獲取(動態獲取)
此處略過該內容,畢竟作為前端應該了解到這裏夠了。如果你想深究此處涉及DHCP服務(通過UDP報文段通信),想了解學習該服務具體細節建議先了解以太網中基於MAC地址的基本通信模式—廣播。
再次假定我們主機已經獲得.本機獲取
本機的IP地址:192.168.1.100
子網掩碼:255.255.255.0
網關的IP地址:192.168.1.1
DNS的IP地址:68.68.68.222
二、 獲取服務器的IP地址
我們輸入的URL是後經過瀏覽器監聽到事件後,經過一系列處理,瀏覽器要生成一個TCP套接字(我們所說的socket),套接字用於給www.abc.com發HTTP請求,為了生成該套接字,我們的主機需要知道www.abc.com的IP地址,此時需要用到DNS服務(基於UDP報文通信),下面是DNS查詢過程
主機的操作系統生成DNS查詢報文,放入UDP報文中,該報文繼續被放到以太網幀中(按照5層網絡結構該處是鏈路層);前面我們已經獲取到了本地DNS服務器地址68.68.68.222,但是鏈路層中該報文傳輸並不能通過IP標識鏈路層中的通信中介,這裏就需要MAC地址(網卡地址),所以需要查詢網關的MAC地址(此處就需要用到ARP協議)
同樣的該處內容復雜繁多,我簡化一下:
DNS查詢報文------》網關路由---------》本地DNS服務器----------》返回我們要訪問的IP地址
上邊過程中本地DNS服務器並不一定就已經緩存了我們所查詢的IP地址,所以我們的查詢IP的請求可能是經過很多次查詢得到的比如:www.abc.com的完整拼寫應該是www.abc.com.
查詢過程由 . ------com.------abc.com.-------www.abc.com.(根域名服務器--頂級域名服務器—二級域名服務器-- www.abc.com.域名服務器)
頂級域名:以.com,.net,.org,.cn等等屬於國際頂級域名,根據目前的國際互聯網域名體系,國際頂級域名分為兩類:類別頂級域名(gTLD)和地理頂級域名(ccTLD)兩種。類別頂級域名是 以"COM"、"NET"、"ORG"、"BIZ"、"INFO"等結尾的域名,均由國外公司負責管理。地理頂級域名是以國家或地區代碼為結尾的域名,如"CN"代表中國,"UK"代表英國。地理頂級域名一般由各個國家或地區負責管理
二級域名:二級域名是以頂級域名為基礎的地理域名,比喻中國的二級域有,.com.cn,.net.cn,.org.cn,.gd.cn等.子域名是其父域名的子域名,比喻父域名是abc.com,子域名就是www.abc.com或者*.abc.com.
一般來說,二級域名是域名的一條記錄,比如alidiedie.com是一個域名,www.alidiedie.com是其中比較常用的記錄,一般默認是用這個,但是類似*.alidiedie.com的域名全部稱作是alidiedie.com的二級
當然查詢一次之後本地域名服務器就會緩存本次查的域名IP,避免下次查詢在經歷這個復雜的過程,如果對域名服務器的網絡結構有興趣可以自行學習,此處不再闡述。
如果我們訪問的服務器使用了代理(如常見的反向代理nginx)那麽DNS查回來的是代理的IP地址(後邊客戶端與瀏覽器交互也多了一層代理通信)
三、 客戶端和服務端交互(TCP和HTTP)
發送正式請求報文之前客戶端(瀏覽器)會和服務器有三次TCP報文段交互,就是我們所稱的三次握手,每次交換報文都是一次完成的請求過程,此處簡化為:
客戶端---SYN=1,seq=client_isn------服務端
服務端---SYN=1,seq=client_isn,ack= client_isn +1------服務端
客戶端---SYN=0,seq=client_isn+1, ack= client_isn +1------服務端

1.瀏覽器生成HTTP請求類似這樣:
GET / HTTP/1.1
Host: www.abc.com
Connection: keep-alive
User-Agent: Mozilla/5.0 (Windows NT 6.1) ……
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Encoding: gzip,deflate,sdch
Accept-Language: zh-CN,zh;q=0.8
Accept-Charset: GBK,utf-8;q=0.7,*;q=0.3
Cookie: ……
我們假定這個部分的長度為6000字節,它會被嵌在TCP數據包之中。
2.瀏覽器生成TCP套接字(socket)將HTTP數據包放入TCP數據包,並且設置接收方(www.abc.com)的端口號80(默認),發送方(本機)的端口(即之前的socket)是一個隨機生成的1024-65535之間的整數,假定為55555。
TCP數據包的標頭長度為20字節,加上嵌入HTTP的數據包,總長度變為5020字節。

3. 然後,TCP數據包再嵌入IP數據包。IP數據包需要設置雙方的IP地址,這是已知的,發送方是192.168.1.100(本機),接收方是172.172.72.222(假定此IP為剛第二部查詢到的服務器IP)。
然後,TCP數據包再嵌入IP數據包。IP數據包需要設置雙方的IP地址,這是已知的,發送方是192.168.1.100(本機),接收方是172.172.72.222。
IP數據包的標頭長度為20字節,加上嵌入的TCP數據包,總長度變為5040字節。

4. 最後數據進入鏈路層,IP數據包嵌入以太網數據包。以太網數據包需要設置雙方的MAC地址,發送方為本機的網卡MAC地址,接收方為網關192.168.1.1的MAC地址(通過ARP協議得到)。
以太網數據包的數據部分,最大長度為1500字節,而現在的IP數據包長度為5040字節。因此,IP數據包必須分割成四個包。因為每個包都有自己的IP標頭(20字節),所以四個包的IP數據包的長度分別為1500、1500、1500、600。
5. 服務器端響應
經過多個網關的轉發,服務器172.172.72.222,收到了這四個以太網數據包。
根據IP標頭的序號,服務器將四個包拼起來,取出完整的TCP數據包,然後讀出裏面的”HTTP請求”,接著做出”HTTP響應”,再用TCP協議發回來。瀏覽器接收到數據解碼後變成HTML文檔。

四、瀏覽器渲染部分:
1、瀏覽器渲染頁面
1.1瀏覽器內核(渲染引擎)從瀏覽器網絡模塊獲取文檔內容後主流程:
a.解析HTML文檔創建文檔對象模型(DOM)
b.解析CSS創建CSS對象模型(CSSDOM)
c.基於DOM和CSSDOM執行JS腳本
d. 基於DOM和CSSDOM構建渲染樹
e.使用渲染樹布局(layout)素有元素
f.瀏覽器UI後端渲染(Paint)所有元素
以上過程是一個漸進過程,即渲染引擎將會盡可能早的把內容在屏幕上顯示出來,不會等到所有的 HTML 都被解析完才開始建造和布局渲染樹,當進程還在繼續解析源源不斷的來自於網絡的內容的時候,一部分內容會被解析並且顯示出來
此處附上webkit和Mozilla‘s Gecko 渲染引擎主要流程圖
技術分享圖片


Webkit渲染引擎主要流程

技術分享圖片
Mozilla‘s Gecko 渲染引擎主要流程


1.2解析(How browsers work)
以上主流程的涉及到的解析主要有:
a.HTML解析,
HTML語法規則並不是上下文無關的,HTML語言特點:
1. 語言寬容的特性
2. 瀏覽器對人們熟知的非法 HTML 有容錯性的事實
3. 解析過程是可中斷的。通常資源在解析過程中是不變的,但是在 HTML 裏,包含“document.write”的腳本可以增加額外的子串,因此解析過程實際上改變了初始內容。
因此HTML 不能用普通的自上向下或者自下向上的普通解析器解析,瀏覽器做了專門的解析器來解析 HTML
b.CSS 解析器
每個css 文件都會被解析成一個 StyleSheet 對象,每個對象包含 css 規則,CSSrule 對象包含選擇器和聲明對象以及符合 CSS 規則的其他對象
c.JS腳本解析和執行
web 模型是同步的,開發者希望當解析器遇到<script>標簽的時候腳本就被解析並且執行,腳本執行完成前暫停文檔的解析,如果腳本是外部的,必須首先從網絡上獲取這個資源—這也是同步的,獲取到這個資源之前會暫停文檔的解析,這是用了多年的模型,當然也在 HTML4 和 5 中被定義了。開發者可以把腳本標記為“defer”,這樣就不會暫停文檔的解析,而是等文檔解析完才執行。HTML5 增加了一個選擇,可以把腳本標記為異步的,這樣就會通過一個不同的線程來解析和執行它
取巧性解析(speculative parsing)
Webkit 和firefox都做了這個優化。當執行腳本的時候,另一個線程會解析其余的文檔,尋找哪些資源需要從網絡上加載並且加載它們,這種方式資源會被平行地載入,整體速度會好一些,要註意—取巧性解析器並不改變 DOM 樹而是把 DOM 留給主解析器,它只解析引用的外部資源,比如外部腳本、樣式表和圖片。
css是不同的模型,理論上來說因為樣式表並不改變 DOM 樹,所以沒有理由為了等待它們而去停止解析文檔,然而卻有一個問題,在文檔解析階段的時候,腳本執行中會請求樣式信息,如果樣式還沒有加載或者解析,腳本會得到錯誤的信息,很明顯這會導致很多問題。這種情況看起來邊緣但實際上很普遍,當樣式表在被加載和解析的時候 Firefox 會阻塞所有的腳本,而只有當腳本試圖訪問某些會被還未加載的樣式影響的屬性的時候,Webkit 才會阻塞它們
1.3構建渲染樹
渲染對象和 DOM 元素是相一致的,但是並不是一對一的關系,非可見元素不會被插入到渲染樹上,有個例子是“head”元素,還有 display 屬性為 none 的元素不會出現在樹中(visibility 為hidden 的元素會出現)
構建渲染樹需要計算每個渲染對象的視覺性的屬性,這是通過計算各個元素的樣式屬性完成的。樣式來自於不同來源的樣式表,內聯樣式或者 HTML 中視覺性的屬性(就像“background”屬性),後者會被轉換以符合 CSS 的樣式屬性
Css的匹配內容較多這裏不過多介紹
1.4布局(layout)和繪制(paint)
布局可以是全局的和遞增的,所謂的“全局”布局發生時由於影響所有解析器的全局樣式改變了如:字體大小改變,屏幕改變;布局也可以是增量的,只有重寫的解析器和他的子孫被布局,當解析器是重寫狀態的時候,增量式布局被觸發(異步的),例如,當額外的內容從網絡中進來並且添加到 DOM 樹之後,新的解析器被添加到渲染樹上的時候。(如前面提到的整個流程是漸進的)
繪制也可以是全局式的(整個樹被繪制)或者增量式的。在增量式的繪制中,解析
器中的一些以不影響整個樹的方式改變。改變了的解析器使屏幕上它的區域無效,這導致操作系統把它看作是“重寫區域”並且觸發“繪制”方法,操作系統很巧妙的做這些,它會把幾個區域合並成一個。在 Chrome 裏變得更加復雜,因為解析器是在一個不同的進程而不是主進程裏,Chrome 在一定程度上模仿操作系統,這種方式會監聽這些事件,並且把消息委托給渲染樹的根節點,這個樹被遍歷直到到達對應的解析器,解析器會重繪它自身(通常還有它的子節點)
以上在布局和繪制過程中涉及兩個概念:reflow(回流)和 repain(重繪),DOM節點的每個元素都是以盒模型的形式存在的,這些都需要瀏覽器去計算其位置和大小等,這個過程稱為reflow;當節點的位置大小和其他內容,字體、顏色等都確定下來之後,這個過程稱為repain。頁面首次加載時候必然會有回流和重繪,回流和重繪制是非常消耗性能的(回流通常比重繪嚴重),尤其是在移動端,所有要盡可能的減少回流和重繪制次數。
2.一些註意的點
如果你對頁面的渲染和js執行順序有疑問可以看一下內容!!
2.1 在1中介紹的瀏覽器渲染頁面的過程是漸進的!!重要的事情說三遍。
2.2以上過程不是有單一線程來完成的,JS執行是單線程,但是瀏覽器不是單線程的;
為了說明這一條我們來看一下瀏覽器的多線程:
一般瀏覽器的內核中至少有三個常駐線程(不同瀏覽器實現不同):
GUI渲染線程 ---- 瀏覽器內核(渲染引擎)的主線程
JS引擎線程 ---- 處理js腳本的主線程
瀏覽器事件觸發線程 ---- 控制交互,響應用戶
另外還有一些執行完就終止的線程,如http請求線程。
我們簡單舉個例子:首先在1中介紹的瀏覽器渲染頁面過程中,解析html頁面構造DOM樹和渲染樹都是有GUI渲染主線程完成的;如果遇到的<script>標簽,會先把js加載回來,交給JS引擎線程解析和執行,在js執行過程中始終是單線程執行,如果你認真看了前面的內容有提到,當js執行過程中會阻塞文檔解析,這是因為js引擎線程和GUI渲染主線程是互斥的,執行js時候渲染主線程處於掛起狀態,故而會阻塞頁面解析,為啥要這樣設計,原因之前也提到過,因為他們同時要操作DOM樹(多個線程同時操作同一個對象會有沖突);
至於css加載和解析時候會阻塞js執行(chrome只有在腳本訪問樣式信息時候才阻塞),之前也提到過是因為js可能訪問css樣式信息;(鑒於js執行和渲染主線程是互斥的,也有瀏覽器把js執行交個渲染主線程執行的說法,這裏我並不是很清楚,畢竟瀏覽器不是我寫的)
2.3 JS引擎線程(以下我們統稱js主線程)執行機制:
(1)所有同步任務都在js主線程上執行,形成一個執行棧(當然也有堆—存儲js執行過程)
(2)主線程之外,還存在一個”任務隊列”。只要異步任務有了運行結果,就在”任務隊列”之中放置一個事件。
(3)一旦”執行棧”中的所有同步任務執行完畢,系統就會讀取”任務隊列”,看看裏面有哪些事件。那些對應的異步任務,於是結束等待狀態,進入執行棧,開始執行。
(4)主線程不斷重復上面的第三步。

主線程從”任務隊列”中讀取事件,這個過程是循環不斷的,所以整個的這種運行機制又稱為Event Loop(事件循環)
技術分享圖片
上圖中,js主線程運行的時候,產生堆(heap)和棧(stack),棧中的代碼調用各種外部API,它們在”任務隊列”中加入各種事件(click,load,done)。只要棧中的代碼執行完畢,主線程就會去讀取”任務隊列”,依次執行那些事件所對應的回調函數。執行棧中的代碼(同步任務),總是在讀取”任務隊列”(異步任務)之前執行.上述過程中監聽DOM事件並將事件回調添加到事件隊列裏是由事件觸發線程完成的。
異步處理的原因:但如果單線程,任務都需要排隊。排隊是因為計算量大,CPU忙不過來,倒也算了,但是很多時候CPU是閑著的,因為IO設備(輸入輸出設備)很慢(比如Ajax操作從網絡讀取數據),不得不等著結果出來,再往下執行。JavaScript語言的設計者意識到,這時主線程完全可以不管IO設備,掛起處於等待中的任務,先運行排在後面的任務。等到IO設備返回了結果,再回過頭,把掛起的任務繼續執行下去。
所以異步是瀏覽器的兩個或者兩個以上線程共同完成的。比如ajax異步請求和setTimeout。
現在你再看xia述代碼有啥區別應該很好理解了
setTimeout(function test( ){
dosomething();
setTimeout(test,100);
},100)
setInterval(unction test( ){
dosomething();
},100)
再者為啥有時候有些代碼裏邊寫
setTimeout(function test( ){
dosomething();
},0)
這些都可以在傷處js執行過程找到答案

參閱:《How browsers work》、《計算機網絡-自頂向下方法》

前端回答從輸入URL到頁面展示都經歷了些什麽