整理本身的筆記,構建知識體系(前端篇 二)

導言

上一篇咱們整理了從瀏覽器接收到url到開啓網絡請求線程,有興趣的同窗能夠看看整理本身的筆記,構建知識體系(前端篇 一),閒話很少說,咱們繼續。javascript

2 開啓網絡請求線程發出完整的http請求

DNS域名解析

DNS(Domain Name System):域名解析服務器css

DNS根域html

根域:目前有13個根集羣服務器,美國10臺,日本1臺,荷蘭1臺,瑞典1臺前端

一級域名:Top Level Domain: tld com, edu, mil, gov, net, org, int,arpa 組織域、國家域(.cn, .ca, .hk, .tw)、反向域 等java

二級域名node

三級域名nginx

最多127級域名es6

如圖:web

域名結構

ICANN(The Internet Corporation for Assigned Names and Numbers)互聯網名稱與數字地址分配機構,負責在全球範圍內對互聯網通用頂級域名(gTLD)以及國家和地區頂級域名(ccTLD)系統的管理、以及根服務器系統的管理算法

DNS工做原理

客戶端向離它最近的DNS服務器發起了查詢請求,通常是由運營商提供 若是代理DNS服務器有記錄則直接能夠返回給客戶端;若是沒有記錄則去根DNS服務器請求,根DNS並不會存儲因此的主機名對應IP的記錄,它只會記錄它的子域的IP,例如.com等後綴的域,代理DNS服務器會拿到.com域的DNS服務器IP 而後再將請求發往.com.域的DNS服務器,若是仍是沒有找到主機,則再往它的下一級找,直到找到具體的主機,把IP返回給客戶端,同時代理DNS服務器也會緩存一份到本地 一次完整的查詢請求通過的流程:Client -->hosts文件 -->DNS Service Local Cache --> DNS Server (recursion) --> Server Cache --> iteration(迭代) --> 根--> 頂級域名DNS-->二級域名DNS…

NDS工做原理

TCP/IP請求

http的本質就是tcp/ip請求

這裏咱們須要瞭解3次握手規則創建鏈接以及斷開鏈接時的四次揮手

tcp將http長報文劃分爲短報文,經過三次握手與服務端創建鏈接,進行可靠傳輸

三次握手的步驟:(抽象派)

客戶端:hello,你是server麼?

服務端:hello,我是server,你是client麼?

客戶端:yes,我是client

創建鏈接成功後,接下來就正式傳輸數據

四次揮手的步驟:(抽象派)

主動方:我已經關閉了向你那邊的主動通道了,只能被動接收了

被動方:收到通道關閉的信息

被動方:那我也告訴你,我這邊向你的主動通道也關閉了

主動方:最後收到數據,以後雙方沒法通訊

爲何鏈接的時候是三次握手,關閉的時候倒是四次握手?

由於當Server端收到Client端的SYN鏈接請求報文後,能夠直接發送SYN+ACK報文。其中ACK報文是用來應答的,SYN報文是用來同步的。可是關閉鏈接時,當Server端收到FIN報文時,極可能並不會當即關閉SOCKET,因此只能先回復一個ACK報文,告訴Client端,"你發的FIN報文我收到了"。只有等到我Server端全部的報文都發送完了,我才能發送FIN報文,所以不能一塊兒發送。故須要四步握手。

五層因特網協議棧

協議

在計算機網絡與信息通信領域裏,人們常常說起 「協議」 一詞。互聯網中經常使用的協議有HTTP、TCP、IP等。

協議的必要性

簡單來講,協議就是計算機與計算機之間經過網絡通訊時,事先達成的一種 「約定」。這種「約定」使不一樣廠商的設備、不一樣的CPU以及不一樣操做系統組成的計算機之間,只要遵循相同的協議就可以實現通訊。這就比如一箇中國人說漢語一個外國人說英語使用不一樣的國家語言進行溝通,怎麼也沒法理解。若是兩我的約定好 都說中文或英文,就能夠互相溝統統信。協議分爲不少種,每一種協議都明確界定了它的行爲規範。兩臺計算機必須可以支持相同的協議,並遵循相同協議進行處理,這樣才能實現相互通訊。

協議分層

網絡協議一般分不一樣層次進行開發,每一層分別負責不一樣的通訊功能

協議層

分層的做用

這兩部分我推薦一篇我的以爲寫得比較詳細並且易懂的文章 TCP/IP詳解

3 服務器接收到請求

服務端在接收到請求時,內部會進行不少的處理,可是本身也只作過一點nodejs後端,也沒有大型高併發項目經驗,因此只是簡單介紹一下,留個概念

負載均衡

對於大型的項目,因爲併發訪問量很大,因此每每一臺服務器是吃不消的,因此通常會有若干臺服務器組成一個集羣,而後配合反向代理實現負載均衡

固然了,負載均衡不止這一種實現方式,這裏不深刻...

簡單的說:

用戶發起的請求都指向調度服務器(反向代理服務器,譬如安裝了nginx控制負載均衡),而後調度服務器根據實際的調度算法,分配不一樣的請求給對應集羣中的服務器執行,而後調度器等待實際服務器的HTTP響應,並將它反饋給用戶

想全面一點了解的同窗請看這裏

後臺處理

通常後臺都是部署到容器中的,因此通常爲:

先是容器接受到請求(如tomcat容器) 而後對應容器中的後臺程序接收到請求(如java程序) 而後就是後臺會有本身的統一處理,處理完後響應響應結果 歸納下:

通常有的後端是有統一的驗證的,如安全攔截,跨域驗證 若是這一步不符合規則,就直接返回了相應的http報文(如拒絕請求等) 而後當驗證經過後,纔會進入實際的後臺代碼,此時是程序接收到請求,而後執行(譬如查詢數據庫,大量計算等等) 等程序執行完畢後,就會返回一個http響應包(通常這一步也會通過多層封裝) 而後就是將這個包從後端發送到前端,完成交互

4 先後臺http交互

http報文

用於HTTP協議交互的信息被稱爲報文。

請求端(客戶端)的HTTP報文叫作請求報文,響應端(服務器端)的叫作響應報文。

HTTP報文自己是由多行數據構成的字符串文本。

HTTP報文大體上可分爲報文首部和報文主體兩塊,二者由最初出現的空行來劃分。

一般,並不必定要有報文主體,報文通常包括了:通用頭部,請求/響應頭部,請求/響應體。

通用頭部

Request Url: 請求的web服務器地址

Request Method: 請求方式 (Get、POST、OPTIONS、PUT、HEAD、DELETE、CONNECT、TRACE)

Status Code: 請求的返回狀態碼,如200表明成功

Remote Address: 請求的遠程服務器地址(會轉爲IP)

譬如,在跨域拒絕時,多是method爲options,狀態碼爲404/405等(固然,實際上可能的組合有不少)

其中,Method的話通常分爲兩批次:

HTTP1.0定義了三種請求方法: GET, POST 和 HEAD方法。

HTTP1.1新增了五種請求方法:OPTIONS, PUT, DELETE, TRACE 和 CONNECT 方法。 這裏面最經常使用到的就是狀態碼,不少時候都是經過狀態碼來判斷 狀態碼大體範圍意思:

請求/響應頭

經常使用請求頭:

Accept: 接收類型,表示瀏覽器支持的MIME類型
(對標服務端返回的Content-Type)

Accept-Encoding:瀏覽器支持的壓縮類型,如gzip等,超出類型不能接收

Content-Type:客戶端發送出去實體內容的類型

Cache-Control: 指定請求和響應遵循的緩存機制,如no-cache

If-Modified-Since:對應服務端的Last-Modified,用來匹配看文件是否變更,只能精確到1s以內,http1.0中

Expires:緩存控制,在這個時間內不會請求,直接使用緩存,http1.0,並且是服務端時間

Max-age:表明資源在本地緩存多少秒,有效時間內不會請求,而是使用緩存,http1.1中

If-None-Match:對應服務端的ETag,用來匹配文件內容是否改變(很是精確),http1.1中

Cookie: 有cookie而且同域訪問時會自動帶上

Connection: 當瀏覽器與服務器通訊時對於長鏈接如何進行處理,如keep-alive

Host:請求的服務器URL

Origin:最初的請求是從哪裏發起的(只會精確到端口),Origin比Referer更尊重隱私

Referer:該頁面的來源URL(適用於全部類型的請求,會精確到詳細頁面地址,csrf攔截經常使用到這個字段)

User-Agent:用戶客戶端的一些必要信息,如UA頭部等
複製代碼

經常使用響應頭:

Access-Control-Allow-Headers: 服務器端容許的請求Headers

Access-Control-Allow-Methods: 服務器端容許的請求方法

Access-Control-Allow-Origin: 服務器端容許的請求Origin頭部(譬如爲*)

Content-Type:服務端返回的實體內容的類型

Date:數據從服務器發送的時間

Cache-Control:告訴瀏覽器或其餘客戶,什麼環境能夠安全的緩存文檔

Last-Modified:請求資源的最後修改時間

Expires:應該在何時認爲文檔已通過期,從而再也不緩存它

Max-age:客戶端的本地資源應該緩存多少秒,開啓了Cache-Control後有效

ETag:請求變量的實體標籤的當前值

Set-Cookie:設置和頁面關聯的cookie,服務器經過這個頭部把cookie傳給客戶端

Keep-Alive:若是客戶端有keep-alive,服務端也會有響應(如timeout=38)

Server:服務器的一些相關信息
複製代碼

http緩存

先後端的http交互中,使用緩存能很大程度上的提高效率,並且基本上對性能有要求的前端項目都是必用緩存的

強緩存與弱緩存

緩存能夠簡單的劃分紅兩種類型:強緩存(200 from cache)與協商緩存(304)(這裏推薦大佬的好文前端進階必備的網絡基礎

區別簡述以下:

強緩存(200 from cache)時,瀏覽器若是判斷本地緩存未過時,就直接使用,無需發起http請求 協商緩存(304)時,瀏覽器會向服務端發起http請求,而後服務端告訴瀏覽器文件未改變,讓瀏覽器使用本地緩存 對於協商緩存,使用Ctrl + F5強制刷新可使得緩存無效

可是對於強緩存,在未過時時,必須更新資源路徑才能發起新的請求(更改了路徑至關因而另外一個資源了,這也是前端工程化中經常使用到的技巧)

緩存頭部簡述 上述提到了強緩存和協商緩存,那它們是怎麼區分的呢?

答案是經過不一樣的http頭部控制

先看下這幾個頭部:

If-None-Match/E-tag、If-Modified-Since/Last-Modified、Cache-Control/Max-Age、Pragma/Expires 這些就是緩存中經常使用到的頭部,這裏不展開。僅列舉下大體使用。

屬於強緩存控制的:

(http1.1)Cache-Control/Max-Age (http1.0)Pragma/Expires 注意:Max-Age不是一個頭部,它是Cache-Control頭部的值

屬於協商緩存控制的:

(http1.1)If-None-Match/E-tag (http1.0)If-Modified-Since/Last-Modified 能夠看到,上述有提到http1.1和http1.0,這些不一樣的頭部是屬於不一樣http時期的

再提一點,其實HTML頁面中也有一個meta標籤能夠控制緩存方案-Pragma

不過,這種方案仍是比較少用到,由於支持狀況不佳,譬如緩存代理服務器確定不支持,因此不推薦

頭部的區別 首先明確,http的發展是從http1.0到http1.1

而在http1.1中,出了一些新內容,彌補了http1.0的不足。

http1.0中的緩存控制:

Pragma:嚴格來講,它不屬於專門的緩存控制頭部,可是它設置no-cache時可讓本地強緩存失效(屬於編譯控制,來實現特定的指令,主要是由於兼容http1.0,因此之前又被大量應用)

Expires:服務端配置的,屬於強緩存,用來控制在規定的時間以前,瀏覽器不會發出請求,而是直接使用本地緩存,注意,Expires通常對應服務器端時間,如Expires:Fri, 30 Oct 1998 14:19:41 If-Modified-Since/Last-Modified:這兩個是成對出現的,屬於協商緩存的內容,其中瀏覽器的頭部是If-Modified-Since,而服務端的是Last-Modified,它的做用是,在發起請求時,若是If-Modified-Since和Last-Modified匹配,那麼表明服務器資源並未改變,所以服務端不會返回資源實體,而是隻返回頭部,通知瀏覽器可使用本地緩存。Last-Modified,顧名思義,指的是文件最後的修改時間,並且只能精確到1s之內

http1.1中的緩存控制:

Cache-Control:緩存控制頭部,有no-cache、max-age等多種取值

Max-Age:服務端配置的,用來控制強緩存,在規定的時間以內,瀏覽器無需發出請求,直接使用本地緩存,注意,Max-Age是Cache-Control頭部的值,不是獨立的頭部,譬如Cache-Control: max-age=3600,並且它值得是絕對時間,由瀏覽器本身計算

If-None-Match/E-tag:這兩個是成對出現的,屬於協商緩存的內容,其中瀏覽器的頭部是If-None-Match,而服務端的是E-tag,一樣,發出請求後,若是If-None-Match和E-tag匹配,則表明內容未變,通知瀏覽器使用本地緩存,和Last-Modified不一樣,E-tag更精確,它是相似於指紋同樣的東西,基於FileEtag INode Mtime Size生成,也就是說,只要文件變,指紋就會變,並且沒有1s精確度的限制。

Max-Age相比Expires?

Expires使用的是服務器端的時間

可是有時候會有這樣一種狀況-客戶端時間和服務端不一樣步

那這樣,可能就會出問題了,形成了瀏覽器本地的緩存無用或者一直沒法過時

因此通常http1.1後不推薦使用Expires

而Max-Age使用的是客戶端本地時間的計算,所以不會有這個問題

所以推薦使用Max-Age。

注意,若是同時啓用了Cache-Control與Expires,Cache-Control優先級高。

E-tag相比Last-Modified?

Last-Modified:

代表服務端的文件最後什麼時候改變的 它有一個缺陷就是隻能精確到1s, 而後還有一個問題就是有的服務端的文件會週期性的改變,致使緩存失效 而E-tag:

是一種指紋機制,表明文件相關指紋 只有文件變纔會變,也只要文件變就會變, 也沒有精確時間的限制,只要文件一遍,立馬E-tag就不同了 若是同時帶有E-tag和Last-Modified,服務端會優先檢查E-tag

各大緩存頭部的總體關係以下圖

cookie

Cookie是什麼? Cookie 是一小段文本信息,伴隨着用戶請求和頁面在 Web 服務器和瀏覽器之間傳遞。Cookie 包含每次用戶訪問站點時 Web 應用程序均可以讀取的信息。

爲何須要Cookie? 由於HTTP協議是無狀態的,對於一個瀏覽器發出的屢次請求,WEB服務器沒法區分 是否是來源於同一個瀏覽器。因此,須要額外的數據用於維護會話。 Cookie 正是這樣的一段隨HTTP請求一塊兒被傳遞的額外數據。

Cookie能作什麼? Cookie只是一段文本,因此它只能保存字符串。並且瀏覽器對它有大小限制以及 它會隨着每次請求被髮送到服務器,因此應該保證它不要太大。 Cookie的內容也是明文保存的,有些瀏覽器提供界面修改,因此, 不適合保存重要的或者涉及隱私的內容。

Cookie 的限制。 大多數瀏覽器支持最大爲 4096 字節的 Cookie。因爲這限制了 Cookie 的大小,最好用 Cookie 來存儲少許數據,或者存儲用戶 ID 之類的標識符。用戶 ID 隨後即可用於標識用戶,以及從數據庫或其餘數據源中讀取用戶信息。 瀏覽器還限制站點能夠在用戶計算機上存儲的 Cookie 的數量。大多數瀏覽器只容許每一個站點存儲 20 個 Cookie;若是試圖存儲更多 Cookie,則最舊的 Cookie 便會被丟棄。有些瀏覽器還會對它們將接受的來自全部站點的 Cookie 總數做出絕對限制,一般爲 300 個。

經過前面的內容,咱們瞭解到Cookie是用於維持服務端會話狀態的,一般由服務端寫入,在後續請求中,供服務端讀取。

經常使用的使用場景:

在登錄頁面,用戶登錄了

此時,服務端會生成一個session,session中有對於用戶的信息(如用戶名、密碼等)

而後會有一個sessionid(至關因而服務端的這個session對應的key)

而後服務端在登陸頁面中寫入cookie,值就是:jsessionid=xxx

而後瀏覽器本地就有這個cookie了,之後訪問同域名下的頁面時,自動帶上cookie,自動檢驗,在有效時間內無需二次登錄。
複製代碼

上述就是cookie的經常使用場景簡述(固然了,實際狀況下得考慮更多因素)

通常來講,cookie是不容許存放敏感信息的(千萬不要明文存儲用戶名、密碼),由於很是不安全,若是必定要強行存儲,首先,必定要在cookie中設置httponly(這樣就沒法經過js操做了),另外能夠考慮rsa等非對稱加密(由於實際上,瀏覽器本地也是容易被攻克的,並不安全)

另外,因爲在同域名的資源請求時,瀏覽器會默認帶上本地的cookie,針對這種狀況,在某些場景下是須要優化的。

譬如如下場景:

客戶端在域名A下有cookie(這個能夠是登錄時由服務端寫入的)

而後在域名A下有一個頁面,頁面中有不少依賴的靜態資源(都是域名A的,譬若有20個靜態資源)

此時就有一個問題,頁面加載,請求這些靜態資源時,瀏覽器會默認帶上cookie

也就是說,這20個靜態資源的http請求,每個都得帶上cookie,而實際上靜態資源並不須要cookie驗證

此時就形成了較爲嚴重的浪費,並且也下降了訪問速度(由於內容更多了)
複製代碼

固然了,針對這種場景,是有優化方案的(多域名拆分)。具體作法就是:

將靜態資源分組,分別放到不一樣的域名下(如static.base.com) 而page.base.com(頁面所在域名)下請求時,是不會帶上static.base.com域名的cookie的,因此就避免了浪費 說到了多域名拆分,這裏再提一個問題,那就是:

在移動端,若是請求的域名數過多,會下降請求速度(由於域名整套解析流程是很耗費時間的,並且移動端通常帶寬都比不上pc) 此時就須要用到一種優化方案:dns-prefetch(讓瀏覽器空閒時提早解析dns域名,不過也請合理使用,勿濫用) 關於cookie的交互,能夠看下圖總結:

長鏈接短連接

長鏈接:client方與server方先創建鏈接,鏈接創建後不斷開,而後再進行報文發送和接收。這種方式下因爲通信鏈接一直存在。此種方式經常使用於P2P通訊。

短鏈接:Client方與server每進行一次報文收發交易時才進行通信鏈接,交易完畢後當即斷開鏈接。此方式經常使用於一點對多點通信。C/S通訊。

長鏈接與短鏈接的操做過程:

短鏈接的操做步驟是:創建鏈接——數據傳輸——關閉鏈接...創建鏈接——數據傳輸——關閉鏈接;

長鏈接的操做步驟是:創建鏈接——數據傳輸...(保持鏈接)...數據傳輸——關閉鏈接

長鏈接與短鏈接的使用時機:

長鏈接

鏈接多用於操做頻繁,點對點的通信,並且鏈接數不能太多的狀況。每一個TCP鏈接的創建都須要三次握手,每一個TCP鏈接的斷開要四次握手。若是每次操做都要 創建鏈接而後再操做的話處理速度會下降,因此每次操做下次操做時直接發送數據就能夠了,不用再創建TCP鏈接。例如:數據庫的鏈接用長鏈接,若是用短鏈接 頻繁的通訊會形成socket錯誤,頻繁的socket建立也是對資源的浪費。

短鏈接

鏈接:web網站的http服務通常都用短鏈接。由於長鏈接對於服務器來講要耗費必定的資源。像web網站這麼頻繁的成千上萬甚至上億客戶端的鏈接用短連 接更省一些資源。試想若是都用長鏈接,並且同時用成千上萬的用戶,每一個用戶都佔有一個鏈接的話,可想而知服務器的壓力有多大。因此併發量大,可是每一個用戶 又不需頻繁操做的狀況下須要短鏈接。

總之:長鏈接和短鏈接的選擇要視需求而定。

http1.0/1.1/2.0

http1.0

早先1.0的HTTP版本,是一種無狀態、無鏈接的應用層協議。

HTTP1.0規定瀏覽器和服務器保持短暫的鏈接,瀏覽器的每次請求都須要與服務器創建一個TCP鏈接,服務器處理完成後當即斷開TCP鏈接(無鏈接),服務器不跟蹤每一個客戶端也不記錄過去的請求(無狀態)。

這種無狀態性能夠藉助cookie/session機制來作身份認證和狀態記錄。而下面兩個問題就比較麻煩了。

首先,無鏈接的特性致使最大的性能缺陷就是沒法複用鏈接。每次發送請求的時候,都須要進行一次TCP的鏈接,而TCP的鏈接釋放過程又是比較費事的。這種無鏈接的特性會使得網絡的利用率很是低。

其次就是隊頭阻塞(head of line blocking)。因爲HTTP1.0規定下一個請求必須在前一個請求響應到達以前才能發送。假設前一個請求響應一直不到達,那麼下一個請求就不發送,一樣的後面的請求也給阻塞了。

爲了解決這些問題,HTTP1.1出現了。

http1.1

對於HTTP1.1,不只繼承了HTTP1.0簡單的特色,還克服了諸多HTTP1.0性能上的問題。

首先是長鏈接,HTTP1.1增長了一個Connection字段,經過設置Keep-Alive能夠保持HTTP鏈接不斷開,避免了每次客戶端與服務器請求都要重複創建釋放創建TCP鏈接,提升了網絡的利用率。若是客戶端想關閉HTTP鏈接,能夠在請求頭中攜帶Connection: false來告知服務器關閉請求。

注意: keep-alive不會永遠保持,它有一個持續時間,通常在服務器中配置(如apache),另外長鏈接須要客戶端和服務器都支持時纔有效

其次,是HTTP1.1支持請求管道化(pipelining)。基於HTTP1.1的長鏈接,使得請求管線化成爲可能。管線化使得請求可以「並行」傳輸。舉個例子來講,假如響應的主體是一個html頁面,頁面中包含了不少img,這個時候keep-alive就起了很大的做用,可以進行「並行」發送多個請求。(注意這裏的「並行」並非真正意義上的並行傳輸,具體解釋以下。)

須要注意的是,服務器必須按照客戶端請求的前後順序依次回送相應的結果,以保證客戶端可以區分出每次請求的響應內容。

也就是說,HTTP管道化可讓咱們把先進先出隊列從客戶端(請求隊列)遷移到服務端(響應隊列)。

http 2.0

http2.0不是https,它至關因而http的下一代規範(譬如https的請求能夠是http2.0規範的)

而後簡述下http2.0與http1.1的顯著不一樣點:

http1.1中,每請求一個資源,都是須要開啓一個tcp/ip鏈接的,因此對應的結果是,每個資源對應一個tcp/ip請求,因爲tcp/ip自己有併發數限制,因此當資源一多,速度就顯著慢下來 http2.0中,一個tcp/ip請求能夠請求多個資源,也就是說,只要一次tcp/ip請求,就能夠請求若干個資源,分割成更小的幀請求,速度明顯提高。 因此,若是http2.0全面應用,不少http1.1中的優化方案就無需用到了(譬如打包成精靈圖,靜態資源多域名拆分等)

而後簡述下http2.0的一些特性:

多路複用(即一個tcp/ip鏈接能夠請求多個資源)

首部壓縮(http頭部壓縮,減小體積)

二進制分幀(在應用層跟傳送層之間增長了一個二進制分幀層,改進傳輸性能,實現低延遲和高吞吐量)

服務器端推送(服務端能夠對客戶端的一個請求發出多個響應,能夠主動通知客戶端)

請求優先級(若是流被賦予了優先級,它就會基於這個優先級來處理,由服務器決定須要多少資源來處理該請求。) 詳細在這裏

https

https中的s表示SSL或者TLS,就是在原http的基礎上加上一層用於數據加密、解密、身份認證的安全層。

通常來講,主要關注的就是SSL/TLS的握手流程,以下(簡述):

1. 瀏覽器請求創建SSL連接,並向服務端發送一個隨機數–Client random和客戶端支持的加密方法,好比RSA加密,此時是明文傳輸。 

2. 服務端從中選出一組加密算法與Hash算法,回覆一個隨機數–Server random,並將本身的身份信息以證書的形式發回給瀏覽器
(證書裏包含了網站地址,非對稱加密的公鑰,以及證書頒發機構等信息)

3. 瀏覽器收到服務端的證書後
    
    - 驗證證書的合法性(頒發機構是否合法,證書中包含的網址是否和正在訪問的同樣),若是證書信任,則瀏覽器會顯示一個小鎖頭,不然會有提示
    
    - 用戶接收證書後(無論信不信任),瀏覽會生產新的隨機數–Premaster secret,而後證書中的公鑰以及指定的加密方法加密`Premaster secret`,發送給服務器。
    
    - 利用Client random、Server random和Premaster secret經過必定的算法生成HTTP連接數據傳輸的對稱加密key-`session key`
    
    - 使用約定好的HASH算法計算握手消息,並使用生成的`session key`對消息進行加密,最後將以前生成的全部信息發送給服務端。 
    
4. 服務端收到瀏覽器的回覆

    - 利用已知的加解密方式與本身的私鑰進行解密,獲取`Premaster secret`
    
    - 和瀏覽器相同規則生成`session key`
    
    - 使用`session key`解密瀏覽器發來的握手消息,並驗證Hash是否與瀏覽器發來的一致
    
    - 使用`session key`加密一段握手消息,發送給瀏覽器
    
5. 瀏覽器解密並計算握手消息的HASH,若是與服務端發來的HASH一致,此時握手過程結束,
複製代碼

詳細這裏

5 解析頁面

前面有提到http交互,那麼接下來就是瀏覽器獲取到html,而後解析,渲染

流程簡述

瀏覽器內核拿到內容後,渲染步驟大體能夠分爲如下幾步:

1. 解析HTML,構建DOM樹

2. 解析CSS,生成CSS規則樹

3. 合併DOM樹和CSS規則,生成render樹

4. 佈局render樹(Layout/reflow),負責各元素尺寸、位置的計算

5. 繪製render樹(paint),繪製頁面像素信息

6. 瀏覽器會將各層的信息發送給GPU,GPU會將各層合成(composite),顯示在屏幕上
複製代碼

如圖:

html解析生成dom樹

整個渲染步驟中,HTML解析是第一步。

簡單的理解,這一步的流程是這樣的:瀏覽器解析HTML,構建DOM樹。

但實際上,在分析總體構建時,卻不能一筆帶過,得稍微展開。

解析HTML到構建出DOM固然過程能夠簡述以下:

Bytes → characters → tokens → nodes → DOM
譬如假設有這樣一個HTML頁面:
複製代碼
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <link href="style.css" rel="stylesheet">
    <title>Critical Path</title>
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg"></div>
  </body>
</html>
複製代碼

瀏覽器的處理以下:

重點過程:

1. Conversion轉換:瀏覽器將得到的HTML內容(Bytes)基於他的編碼轉換爲單個字符

2. Tokenizing分詞:瀏覽器按照HTML規範標準將這些字符轉換爲不一樣的標記token。每一個token都有本身獨特的含義以及規則集

3. Lexing詞法分析:分詞的結果是獲得一堆的token,此時把他們轉換爲對象,這些對象分別定義他們的屬性和規則

4. DOM構建:由於HTML標記定義的就是不一樣標籤之間的關係,這個關係就像是一個樹形結構同樣
例如:body對象的父節點就是HTML對象,而後段略p對象的父節點就是body對象
複製代碼

css解析生成樣式樹

同理,CSS規則樹的生成也是相似。簡述爲:

Bytes → characters → tokens → nodes → CSSOM
譬如style.css內容以下:

body { font-size: 16px }
p { font-weight: bold }
span { color: red }
p span { display: none }
img { float: right }
複製代碼

那麼最終的CSSOM樹就是:

渲染

渲染樹

當DOM樹和CSSOM都有了後,就要開始構建渲染樹了

通常來講,渲染樹和DOM樹相對應的,但不是嚴格意義上的一一對應

由於有一些不可見的DOM元素不會插入到渲染樹中,如head這種不可見的標籤或者display: none等

總體來講能夠看圖:

渲染流程

圖中重要的四個步驟就是:

1. 計算CSS樣式

2. 構建渲染樹

3. 佈局,主要定位座標和大小,是否換行,各類position overflow z-index屬性

4. 繪製,將圖像繪製出來
複製代碼

而後,圖中的線與箭頭表明經過js動態修改了DOM或CSS,致使了從新佈局(Layout)或渲染(Repaint)

這裏Layout和Repaint的概念是有區別的:

Layout,也稱爲Reflow,即迴流。通常意味着元素的內容、結構、位置或尺寸發生了變化,須要從新計算樣式和渲染樹 Repaint,即重繪。意味着元素髮生的改變只是影響了元素的一些外觀之類的時候(例如,背景色,邊框顏色,文字顏色等),此時只須要應用新樣式繪製這個元素就能夠了 迴流的成本開銷要高於重繪,並且一個節點的迴流每每回致使子節點以及同級節點的迴流, 因此優化方案中通常都包括,儘可能避免迴流。

什麼會引發迴流?

1.頁面渲染初始化

2.DOM結構改變,好比刪除了某個節點

3.render樹變化,好比減小了padding

4.窗口resize

5.最複雜的一種:獲取某些屬性,引起迴流,
不少瀏覽器會對迴流作優化,會等到數量足夠時作一次批處理迴流,
可是除了render樹的直接變化,當獲取一些屬性時,瀏覽器爲了得到正確的值也會觸發迴流,這樣使得瀏覽器優化無效,包括
    (1)offset(Top/Left/Width/Height)
     (2) scroll(Top/Left/Width/Height)
     (3) cilent(Top/Left/Width/Height)
     (4) width,height
     (5) 調用了getComputedStyle()或者IE的currentStyle
複製代碼

迴流必定伴隨着重繪,重繪卻能夠單獨出現

因此通常會有一些優化方案,如:

減小逐項更改樣式,最好一次性更改style,或者將樣式定義爲class並一次性更新 避免循環操做dom,建立一個documentFragment或div,在它上面應用全部DOM操做,最後再把它添加到window.document 避免屢次讀取offset等屬性。沒法避免則將它們緩存到變量 將複雜的元素絕對定位或固定定位,使得它脫離文檔流,不然迴流代價會很高

簡單層與複合層

簡單介紹下:

能夠認爲默認只有一個複合圖層,全部的DOM節點都是在這個複合圖層下的 若是開啓了硬件加速功能,能夠將某個節點變成複合圖層 複合圖層之間的繪製互不干擾,由GPU直接控制 而簡單圖層中,就算是absolute等佈局,變化時不影響總體的迴流,可是因爲在同一個圖層中,仍然是會影響繪製的,所以作動畫時性能仍然很低。而複合層是獨立的,因此通常作動畫推薦使用硬件加速

詳細簡單層與複合層

js引擎解析

JS的解釋階段 首先得明確: JS是解釋型語音,因此它無需提早編譯,而是由解釋器實時運行

引擎對JS的處理過程能夠簡述以下:

1. 讀取代碼,進行詞法分析(Lexical analysis),而後將代碼分解成詞元(token)

2. 對詞元進行語法分析(parsing),而後將代碼整理成語法樹(syntax tree)

3. 使用翻譯器(translator),將代碼轉爲字節碼(bytecode)

4. 使用字節碼解釋器(bytecode interpreter),將字節碼轉爲機器碼
最終計算機執行的就是機器碼。
複製代碼

爲了提升運行速度,現代瀏覽器通常採用即時編譯(JIT-Just In Time compiler)

即字節碼只在運行時編譯,用到哪一行就編譯哪一行,而且把編譯結果緩存(inline cache)

這樣整個程序的運行速度能獲得顯著提高。

並且,不一樣瀏覽器策略可能還不一樣,有的瀏覽器就省略了字節碼的翻譯步驟,直接轉爲機器碼(如chrome的v8)

總結起來能夠認爲是: 核心的JIT編譯器將源碼編譯成機器碼運行

JS的預處理階段 上述將的是解釋器的總體過程,這裏再提下在正式執行JS前,還會有一個預處理階段 (譬如變量提高,分號補全等)

預處理階段會作一些事情,確保JS能夠正確執行,這裏僅提部分:

分號補全

JS執行是須要分號的,但爲何如下語句卻能夠正常運行呢?

console.log('a')

console.log('b')
複製代碼

緣由就是JS解釋器有一個Semicolon Insertion規則,它會按照必定規則,在適當的位置補充分號

譬如列舉幾條自動加分號的規則:

當有換行符(包括含有換行符的多行註釋),而且下一個token無法跟前面的語法匹配時,會自動補分號。 當有}時,若是缺乏分號,會補分號。 程序源代碼結束時,若是缺乏分號,會補分號。 因而,上述的代碼就變成了

console.log('a');

console.log('b');
複製代碼

因此能夠正常運行

固然了,這裏有一個經典的例子:

function b() {
    return
    {
        a: 'a'
    };
}
複製代碼

因爲分號補全機制,因此它變成了:

function b() {
    return;
    {
        a: 'a'
    };
}
複製代碼

因此運行後是undefined

變量提高

通常包括函數提高和變量提高

譬如:

a = 1;
b();
function b() {
    console.log('b');
}
var a;
複製代碼

通過變量提高後,就變成:

function b() {
    console.log('b');
}
var a;
a = 1;
b();
複製代碼

這裏沒有展開,其實展開也能夠牽涉到不少內容的

譬如能夠提下變量聲明,函數聲明,形參,實參的優先級順序,以及es6中let有關的臨時死區等

JS的執行階段

這裏能夠參考深刻理解javascript系列(10) 大叔講解的十分到位

回收機制

JS有垃圾處理器,因此無需手動回收內存,而是由垃圾處理器自動處理。

通常來講,垃圾處理器有本身的回收策略。

譬如對於那些執行完畢的函數,若是沒有外部引用(被引用的話會造成閉包),則會回收。(固然通常會把回收動做切割到不一樣的時間段執行,防止影響性能)

經常使用的兩種垃圾回收規則是:

標記清除, 引用計數

Javascript引擎基礎GC方案是(simple GC):mark and sweep(標記清除),簡單解釋以下:

遍歷全部可訪問的對象。 回收已不可訪問的對象。 譬如:(出自javascript高程)

當變量進入環境時,例如,在函數中聲明一個變量,就將這個變量標記爲「進入環境」。從邏輯上講,永遠不能釋放進入環境的變量所佔用的內存,由於只要執行流進入相應的環境,就可能會用到它們。 而當變量離開環境時,則將其標記爲「離開環境」。 垃圾回收器在運行的時候會給存儲在內存中的全部變量都加上標記(固然,可使用任何標記方式)。 而後,它會去掉環境中的變量以及被環境中的變量引用的變量的標記(閉包,也就是說在環境中的以及相關引用的變量會被去除標記)。 而在此以後再被加上標記的變量將被視爲準備刪除的變量,緣由是環境中的變量已經沒法訪問到這些變量了。 最後,垃圾回收器完成內存清除工做,銷燬那些帶標記的值並回收它們所佔用的內存空間。 關於引用計數,簡單點理解:

跟蹤記錄每一個值被引用的次數,當一個值被引用時,次數+1,減持時-1,下次垃圾回收器會回收次數爲0的值的內存(固然了,容易出循環引用的bug)

GC的缺陷

和其餘語言同樣,javascript的GC策略也沒法避免一個問題: GC時,中止響應其餘操做

這是爲了安全考慮。

而Javascript的GC在100ms甚至以上

對通常的應用還好,但對於JS遊戲,動畫對連貫性要求比較高的應用,就麻煩了。

這就是引擎須要優化的點: 避免GC形成的長時間中止響應。

GC優化策略

這裏介紹經常使用到的:分代回收(Generation GC)

目的是經過區分「臨時」與「持久」對象:

多回收「臨時對象」區(young generation)

少回收「持久對象」區(tenured generation)

減小每次需遍歷的對象,從而減小每次GC的耗時。

像node v8引擎就是採用的分代回收(和java同樣,做者是java虛擬機做者。)

結語

本文僅是梳理下知識體系骨架,便於你們記憶,造成本身的知識樹,開支闊葉。學海無涯,砥礪前行,不負初心!

再次感謝大佬們的分享,順便提一下上篇的小問題 我給的答案是 8 只,不知道是否是最少的,請大佬們指教。

相關文章
相關標籤/搜索