~~~~~如有錯誤,歡迎吐槽~~~~~~
前端開發中我們常常要考慮首屏加載時間,為了盡可能減少首屏加載時間我們需要弄清楚從輸入網址到頁面最終呈現的過程都發生了什么事情(感覺很神秘呀!!)然后才能具體問題具體分析,最終達到提升網頁性能的目的。
下面我們揭秘這層神秘的面紗,從輸入網址到頁面呈現大致可分為以下流程:
1.網絡通信
? ? (1).用戶在地址欄輸入網址,瀏覽器進行地址解析。
? ? (2).應用層將解析出的域名進行域名解析。
? ? (3).傳輸層進行tcp三次握手,建立tcp連接。
? ? (4).應用層客戶端向web服務器發送HTTP請求。
? ? (5).網絡層IP協議查詢MAC地址
? ? (6).服務器收到處理請求。
? ? (7).服務器發送HTTP響應報文,瀏覽器收到服務器響應,得到html代碼
2.頁面渲染
? ??(1).解析HTML
????(2).構建DOM樹
????(3).DOM樹與CSS樣式進行附著構造呈現(render)樹
????(4).布局
????(5).繪制
看完上面的流程,寶寶還是一臉懵逼呀-><-?別怕,接下來小波為大家嘻嘻介紹~~~~
網絡通信
1.用戶在地址欄輸入網址,瀏覽器進行地址解析。
我們以下面這個URL為例子,介紹下普通URL的各部分組成http://www.aspxfans.com:8080/news/index.asp?boardID=5&ID=24618&page=1#name
(1).協議部分:該URL的協議部分為“http:”,這代表網頁使用的是HTTP協議。在Internet中可以使用多種協議,如HTTP,FTP等等本例中使用的是HTTP協議。在"HTTP"后面的“//”為分隔符。
(2).域名部分:該URL的域名部分為“www.aspxfans.com”。一個URL中,也可以使用IP地址作為域名使用。
(3).端口部分:跟在域名后面的是端口,域名和端口之間使用“:”作為分隔符。端口不是一個URL必須的部分,如果省略端口部分,將采用默認端口。
(4).虛擬目錄部分:從域名后的第一個“/”開始到最后一個“/”為止,是虛擬目錄部分。虛擬目錄也不是一個URL必須的部分。本例中的虛擬目錄是“/news/”。
(5).文件名部分:從域名后的最后一個“/”開始到“?”為止,是文件名部分,如果沒有“?”,則是從域名后的最后一個“/”開始到“#”為止,是文件部分,如果沒有“?”和“#”,那么從域名后的最后一個“/”開始到結束,都是文件名部分。本例中的文件名是“index.asp”。文件名部分也不是一個URL必須的部分,如果省略該部分,則使用默認的文件名。
(6).錨部分:從“#”開始到最后,都是錨部分。本例中的錨部分是“name”。錨部分也不是一個URL必須的部分。
(7).參數部分:從“?”開始到“#”為止之間的部分為參數部分,又稱搜索部分、查詢部分。本例中的參數部分為“boardID=5&ID=24618&page=1”。參數可以允許有多個參數,參數與參數之間用“&”作為分隔符。
URL解析完成后我們已經手握域名,接下來進行域名解析。
2.將解析出的域名進行域名解析。
~~~《小插曲:小仙女什么是域名解析呀??就是域名到IP地址的轉換過程,IP地址是網路上標識你站點的數字地址,為了簡單好記,采用域名來替代IP地址。域名的解析工作由DNS服務器完成。DNS協議提供通過域名查找IP地址,或逆向從IP地址反查域名的服務》~~~
域名解析過程
(1).查找瀏覽器緩存,因為瀏覽器緩存會記錄DNS記錄一段時間,?有趣的是,操作系統沒有告訴瀏覽器儲存DNS記錄的時間,這樣不同瀏覽器會儲存個自固定的一個時間(2分鐘到30分鐘不等)。
注:瀏覽器的DNS緩存查看和清除
a. chrome中
查看:網址欄輸入:chrome://net-internals/#dns 或 輸入chrome://chrome-urls/就可看到chrome所有的配置界面,然后選擇chrome://dns或者chrome://net-internals/#dns之后再點擊DNS就可查看。
清除:在chrome://net-internals/#dns頁面中,點擊“Clear host cache”,然后選擇“clear cache”和“flush socket”,可以清空chrome的dns緩存。
(2).查詢操作系統緩存
(3).hosts查詢
windows系統在C:\Windows\Syatem32\driver\etc\hosts文件中查找;—Linux系統從/etc/hosts文件中查找。
(4).請求本地域名服務器(可以認為是你的網絡接入服務器商提供,比如中國電信,中國移動,阿里云等域名供應商),如果該服務器有緩存,則直接返回,若沒有,則下一步。。。一般80%到這里就可以了(比如你申請一個域名,去阿里云,那么你肯定會寫上域名所指向的IP啊)
(5).從頂級域名中開始查找:根據本地DNS服務器的設置(是否設置轉發器)進行查詢,如果未用轉發模式,本地DNS就把請求發至13臺根DNS,根DNS服務器收到請求后會判斷這個域名(.com)是誰來授權管理,并會返回一個負責該頂級域名服務器的一個IP。本地DNS服務器收到IP信息后,將會聯系負責.com域的這臺服務器。這臺負責.com域的服務器收到請求后,如果自己無法解析,它就會找一個管理.com域的下一級DNS服務器地址(baidu.com)給本地DNS服務器。當本地DNS服務器收到這個地址后,就會找baidu.com域服務器,重復上面的動作,進行查詢,直至找到www.baidu.com主機。
~~~小可愛們注意啦:上面4步中按照順序,任何一步查找成功,后面步驟步不用再經歷啦
科普時間到啦!!快過來吃瓜~~~什么是dns劫持??何可攻擊根域名服務器的節點,發生在上面第四步,從DNS緩存數據庫中找到時被惡意改為其他網址,所以就請求到了其他的網址。
域名解析后我們的法寶又升級為IP地址了,通過協議可以獲得端口(HTTP:80,HTTP:443),下一步該進行瀏覽器與服務器之間的鏈接。
3.進行tcp三次握手,建立tcp連接(傳輸層)
位于傳輸層的TCP協議為傳輸報文提供可靠的字節流服務。為了方便傳輸,將大塊的數據分割成以報文段為單位的數據包進行管理,并為它們編號,方便服務器接收時能準確地還原報文信息。TCP協議通過“三次握手”等方法保證傳輸的安全可靠。
TCP建立連接過程(三次握手):
客戶端發送一個TCP包。設置SYN=1(請求建立連接)、Seq=X(隨機產生的序列號)
服務器發回確認包(ACK)應答。SYN=1、ACK=1、ACK number = X+1、Seq = Y(隨機產生)
客戶端再次發送確認包(ACK) 。SYN=0、ACK=1、ACK number= Y+1、Seq = X+1
TCP斷開連接過程(四次揮手):
客戶機給服務器一個FIN為1的TCP報文
服務器返回給客戶端一個確認ACK報文
服務器給客戶端發送一個FIN報文
客戶機回復ACK報文
4.客戶端向web服務器發送HTTP請求(應用層)
HTTP請求消息結構
客戶端發送一個HTTP請求到服務器的請求消息包括以下格式:請求行(request line),請求頭部(header),空行和請求數據四部分組成。
第一部分:請求行,用來說明請求類型,要訪問的資源以及所使用的HTTP版本。
第二部分:請求頭部,緊接著請求行之后的部分,用來說明服務器要使用的附加信息,包含很多有關客戶端環境和請求正文的有用信息。例如:請求頭可以聲明瀏覽器所用的語言,請求正文的長度。
第三部分:空行,請求頭部后面的空行是必須的,即使第四部分的請求數據為空,也必須有空行。
第四部分:請求數據也叫主體,可以添加任意的其他數據
HTTP響應消息結構
HTTP響應也由四部分組成:狀態行,消息報頭,空行,響應正文
第一部分:狀態行,右HTTP協議版本號,狀態碼,狀態消息三部分組成
第二部分:消息報頭,用來說明客戶端要使用的一些附加信息。
第三部分:空行,消息報頭后面的空行是必須的。
第四部分:響應正文,服務器返回給客戶端的文本信息。空行后面的html部分為響應正文。
5.網絡層IP協議查詢MAC地址
IP協議的作用是把TCP分割好的各種數據包傳送給接收方。而要保證確實能傳到接收方還需要接收方的MAC地址,也就是物理地址。IP地址和MAC地址是一一對應的關系,一個網絡設備的IP地址可以更換,但是MAC地址一般是固定不變的。**ARP協議可以將IP地址解析成對應的MAC地址。**當通信的雙方不在同一個局域網時,需要多次中轉才能到達最終的目標,在中轉的過程中需要通過下一個中轉站的MAC地址來搜索下一個中轉目標。
ARP 協議是網絡層的協議,但是它所工作的內容是數據鏈路層的。
6.服務器收到處理請求
接收端的服務器在鏈路層收到數據包,再層層向上直到應用層,這過程中包括在運輸層通過TCP協議講分段的數據包重新組成原來的HTTP請求報文。服務接收到客戶端發送的HTTP請求后,查找客戶端請求的資源,并返回響應報文,響應報文中包括一個重要的信息——狀態碼。狀態碼由三位數字組成,其中比較常見的是200 OK表示請求成功。301表示永久重定向,即請求的資源已經永久轉移到新的位置。在返回301狀態碼的同時,響應報文也會附帶重定向的url,客戶端接收到后將http請求的url做相應的改變再重新發送。404 not found 表示客戶端請求的資源找不到
7.服務器發送HTTP響應報文,瀏覽器收到服務器響應,得到html代碼
服務接收到客戶端發送的HTTP請求后,查找客戶端請求的資源,并返回響應報文,響應報文中包括一個重要的信息——狀態碼。狀態碼由三位數字組成,其中比較常見的是200 OK表示請求成功。301表示永久重定向,即請求的資源已經永久轉移到新的位置。在返回301狀態碼的同時,響應報文也會附帶重定向的url,客戶端接收到后將http請求的url做相應的改變再重新發送。404 not found 表示客戶端請求的資源找不到。
接下來瀏覽器就要進行頁面渲染了~~~
頁面渲染
頁面渲染基本流程:解析html以構建dom樹->構建render樹->布局render樹-> 繪制render樹
先上圖:
由圖可知,瀏覽器會解析三種東西:
(1)HTML/SVG/XHTML,解析這三種文件會產生一個DOM Tree。DOM Tree的構建是一個深度遍歷的過程:當前節點的子節點都構建好之后才會構建當前節點的下一個兄弟節點。
(2)CSS,解析CSS會產生CSS樹。
(3)JavaScript腳本,主要是通過DOM API 和 CSSOM API來操作 DOM Tree和 CSS Rule Tree。根據DOM樹和CSSOM來構造Rendering Tree。注意啦~~Rendering Tree渲染樹并不等同于DOM樹,因為一些像Header或者display:none的東西就沒有必要放在渲染樹中了。
有了Render Tree,瀏覽器已經知道網頁中有哪些節點,各個節點的CSS定義以及他們的從屬關系,下一步操作稱之為Layout,即計算尺每個節點在屏幕中的位置。最后一步就是繪制了
重點來啦:
《how browsers work》里面講過一句話:上述這個過程是逐步完成的,為了更好的用戶體驗,渲染引擎將會盡可能早的將內容呈現到屏幕上,并不會等到所有的html都解析完成之后再去構建和布局render樹。它是解析完一部分內容就顯示一部分內容,同時,可能還在通過網絡下載其余內容。
在網上找到下面這篇關于HTML頁面加載和解析流程的文章,感覺不錯,自帶小板凳,過來吃瓜~~~
科普一下兩個概念:
(1)Reflow(回流):瀏覽器要花時間去渲染,當它發現了某個部分發生了變化影響了布局,那就需要倒回去重新渲染。 比如:頁面初次渲染;瀏覽器窗口大小改變,DOM結構變化; render樹變化,某些元素的尺寸,未知,內容變了;元素字體大小改變,激活CSS偽類(如:hover)。
(2)Repaint(重繪):如果只是改變了某個元素的背景顏色,文字顏色等,不影響元素周圍或內部布局的屬性,將只會引起瀏覽器的repaint,重畫某一部分。
回流一定伴隨著重繪,而重繪卻可以單獨出現,Reflow要比Repaint更花費時間,也就更影響性能。所以在寫代碼的時候,要盡量避免過多的Reflow。
我們需要努力減少reflow/repaint,可通過下面的方式:
(1)不要一條一條地修改 DOM 的樣式。與其這樣,還不如預先定義好 css 的 class,然后修改 DOM 的 className。?
(2)不要把 DOM 結點的屬性值放在一個循環里當成循環里的變量。?
(3)為動畫的 HTML 元件使用 fixed 或 absoult 的 position,那么修改他們的 CSS 是不會 reflow 的。?
(4)千萬不要使用 table 布局。因為可能很小的一個小改動會造成整個 table 的重新布局。
編寫css時應當注意:
CSS選擇符是從右到左進行匹配的。從右到左!所以,#nav li 我們以為這是一條很簡單的規則,秒秒鐘就能匹配到想要的元素,但是,但是,但是,是從右往左匹配啊,所以,會去找所有的li,然后再去確定它的父元素是不是#nav。因此,寫css的時候需要注意:
(1)dom深度盡量淺。
(2)減少inline javascript、css的數量。
(3)使用現代合法的css屬性。
(4)不要為id選擇器指定類名或是標簽,因為id可以唯一確定一個元素。
(5)避免后代選擇符,盡量使用子選擇符。原因:子元素匹配符的概率要大于后代元素匹配符。后代選擇符;#tp p{} 子選擇符:#tp>p{}
(6)避免使用通配符,舉一個例子,.mod .hd *{font-size:14px;} 根據匹配順序,將首先匹配通配符,也就是說先匹配出通配符,然后匹配.hd(就是要對dom樹上的所有節點進行遍歷他的父級元素),然后匹配.mod,這樣的性能耗費可想而知.
關于Script標簽:
我們大多會將script標簽放在body結束標簽之前,為啥呀??
(1)js代碼在加載完成后,是立即執行的
(2)js在執行時會阻塞頁面后續內容(包括頁面的渲染、其它資源的下載)即會阻塞Dom樹的構建,原因:因為瀏覽器需要一個穩定的DOM樹結構,而JS中很有可能有代碼直接改變了DOM樹結構,比如使用 document.write 或 appendChild,甚至是直接使用的location.href進行跳轉,瀏覽器為了防止出現JS修 改DOM樹,需要重新構建DOM樹的情況,所以 就會阻塞其他的下載和呈現。
減少JavaScript對性能的影響的方法:
1. 將所有的script標簽放到頁面底部,也就是body閉合標簽之前,這能確保在腳本執行前頁面已經完成了DOM樹渲染。
2. 盡可能地合并腳本。頁面中的script標簽越少,加載也就越快,響應也越迅速。無論是外鏈腳本還是內嵌腳本都是如此。
3. 采用無阻塞下載 JavaScript 腳本的方法:?
(1)使用script標簽的 defer 屬性(僅適用于 IE 和 Firefox 3.5 以上版本); script標簽的defer屬性規定了是否對腳本進行延遲,直到頁面加載為止。
(2)使用動態創建的script元素來下載并執行代碼;
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?~~~~~聽大神說寫博客可以成為小仙女~~~~~