摘要:通用頭部這也是開發人員見過的最多的信息,包括如下請求的服務器地址請求方式請求的返回狀態碼,如代表成功請求的遠程服務器地址會轉為譬如,在跨域拒絕時,可能是為,狀態碼為等當然,實際上可能的組合有很多。
面試中經常會被問到這個問題吧,唉,我最開始被問到的時候也就能大概說一些流程。被問得多了,自己就想去找找這個問題的全面回答,于是乎搜了很多資料和網上的文章,根據那些文章寫一個總結。
寫得不好,或者有意見的直接噴,不用走流程。也歡迎大佬指點
首先這不是小問題,能把里面的過程說清楚真的很麻煩,然后下面我把這些知識點,按流程的形式總結的:
從瀏覽器接收url到開啟網絡請求線程
開啟網絡線程到發出一個完整的http請求
從服務器接收到請求到對應后臺接收到請求
后臺和前臺的http交互
http的緩存問題
瀏覽器接收到http數據包后的解析流程
CSS的可視化格式模型
JS引擎解析過程
跨域、web安全、hybrid等等
1. 從瀏覽器接收url到開啟網絡請求線程多進程的瀏覽器
瀏覽器是多進程的,有一個主控進程,以及每一個tab頁面都會新開一個進程(某些情況下多個tab會合并進程)。
進程可能包括主控進程,插件進程,GPU,tab頁(瀏覽器內核)等等。
Browser進程:瀏覽器的主進程(負責協調、主控),只有一個
第三方插件進程:每種類型的插件對應一個進程,僅當使用該插件時才創建
GPU進程:最多一個,用于3D繪制
瀏覽器渲染進程(內核):默認每個Tab頁面一個進程,互不影響,控制頁面渲染,腳本執行,事件處理等(有時候會優化,如多個空白tab會合并成一個進程)
多線程的瀏覽器內核
每一個tab頁面可以看作是瀏覽器內核進程,然后這個進程是多線程的,它有幾大類子線程:
GUI渲染線程
JS引擎線程
事件觸發線程
定時器觸發線程
http異步網絡請求線程
解析URL
輸入URL后,會進行解析(URL的本質就是統一資源定位符)
URL一般包括幾大部分:
protocol,協議頭,譬如有http,ftp,https等
host,主機域名或IP地址
port,端口號
path,目錄路徑
query,即查詢參數
fragment,即 #后的hash值,一般用來定位到某個位置
網絡請求都是多帶帶的線程
每次網絡請求時都需要開辟多帶帶的線程進行,譬如如果URL解析到http協議,就會新建一個網絡線程去處理資源下載。
因此瀏覽器會根據解析出得協議,開辟一個網絡線程,前往請求資源。
2. 開啟網絡線程到發出一個完整的http請求DNS查詢得到IP
如果輸入的是域名,需要進行dns解析成IP,大致流程:
如果瀏覽器有緩存,直接使用瀏覽器緩存,否則使用本機緩存,再沒有的話就是用host
如果本地沒有,就向dns域名服務器查詢(當然,中間可能還會經過路由,也有緩存等),查詢到對應的IP
注意,域名查詢時有可能是經過了CDN調度器的(如果有cdn存儲功能的話)。
而且,需要知道dns解析是很耗時的,因此如果解析域名過多,會讓首屏加載變得過慢,可以考慮 dns-prefetch優化。
這一塊可以深入展開,具體請去網上搜索,這里就不占篇幅了(網上可以看到很詳細的解答)。
tcp/ip請求
http的本質就是 tcp/ip請求。
需要了解三次握手規則建立連接以及斷開連接時的四次揮手。
tcp將http長報文劃分為短報文,通過三次握手與服務端建立連接,進行可靠傳輸。
三次握手:
1.客戶端給服務器發確實是當前服務器
2.服務器給客戶端回應,我是你要訪問的當前服務器
3.客戶端回應,我是客戶端
四次揮手:
1.發起者:關閉主動傳輸信息的通道,只能接收信息
2.接受者:收到通道關閉的信息
3.接受者:也關閉主動傳輸信息的通道
4.發起者:接收到數據,關閉通道,雙方無法通信
tcp/ip的并發限制
瀏覽器對同一域名下并發的tcp連接是有限制的(2-10個不等)。
而且在http1.0中往往一個資源下載就需要對應一個tcp/ip請求。
所以針對這個瓶頸,又出現了很多的資源優化方案。(感興趣的朋友請自行搜索,資料很多)
get和post的區別
這個東西網上的資料也很多,這兒就大概描述一下在tcp/ip層面的區別,在http層面的區別請讀者自行搜索:
get和post本質都是tcp/ip。
get會產生一個tcp數據包,post兩個。
具體就是:
get請求時,瀏覽器會把 headers和 data一起發送出去,服務器響應200(返回數據), post請求時,瀏覽器先發送 headers,服務器響應 100continue,瀏覽器再發送 data,服務器響應200(返回數據)。
然后有讀者可能以前了解過OSI的七層:物理層、 數據鏈路層、 網絡層、 傳輸層、 會話層、 表示層、 應用層
這兒就不班門弄虎了,列一下內容,需要深入理解的讀者請自行搜索,計算機網絡相關的資料。
1.應用層(dns,http) DNS解析成IP并發送http請求
2.傳輸層(tcp,udp) 建立tcp連接(三次握手)
3.網絡層(IP,ARP) IP尋址
4.數據鏈路層(PPP) 封裝成幀
5.物理層(利用物理介質傳輸比特流) 物理傳輸(然后傳輸的時候通過雙絞線,電磁波等各種介質)
6.表示層:主要處理兩個通信系統中交換信息的表示方式,包括數據格式交換,數據加密與解密,數據壓縮與終端類型轉換等
7.會話層:它具體管理不同用戶和進程之間的對話,如控制登陸和注銷過程
后端的操作有點多,我這兒也就不秀自己知識面低下了,哈哈
負載均衡
對于大型的項目,由于并發訪問量很大,所以往往一臺服務器是吃不消的,所以一般會有若干臺服務器組成一個集群,然后配合反向代理實現負載均衡。(據說現在node在微服務的項目方面越來越猛,大并發也不在話下,正在研究node,希望后面能寫一個心得)
簡單的說:用戶發起的請求都指向調度服務器(反向代理服務器,譬如安裝了nginx控制負載均衡),然后調度服務器根據實際的調度算法,分配不同的請求給對應集群中的服務器執行,然后調度器等待實際服務器的HTTP響應,并將它反饋給用戶。
后臺的處理
一般后臺都是部署到容器中的,所以一般為:
1.先是容器接受到請求(如tomcat容器)
2.然后對應容器中的后臺程序接收到請求(如java程序)
3.然后就是后臺會有自己的統一處理,處理完后響應響應結果
概括下:
1.一般有的后端是有統一的驗證的,如安全攔截,跨域驗證
2.如果這一步不符合規則,就直接返回了相應的http報文(如拒絕請求等)
3.然后當驗證通過后,才會進入實際的后臺代碼,此時是程序接收到請求,然后執行(譬如查詢數據庫,大量計算等等)
4.等程序執行完畢后,就會返回一個http響應包(一般這一步也會經過多層封裝)
5.然后就是將這個包從后端發送到前端,完成交互
前后端交互時,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 方法。
相信知道RESTFUL的讀者應該很熟悉,現在在前端后端開發使用頻繁的也就是get,post,put,delete,也是我們熟知的四大操作"增刪改查"。
狀態碼:這是進行請求和回應的關鍵信息,官方有最全的狀態碼信息,這兒就列幾個常見的:
200——表明該請求被成功地完成,所請求的資源發送回客戶端 304——自從上次請求后,請求的網頁未修改過,請客戶端使用本地緩存 400——客戶端請求有錯(譬如可以是安全模塊攔截) 401——請求未經授權 403——禁止訪問(譬如可以是未登錄時禁止) 404——資源未找到 500——服務器內部錯誤 503——服務不可用
其他的請讀者自行去搜索官方介紹。
對于狀態碼:
數字1開頭的表示:請求已經接收,繼續處理
數字2開頭的表示:請求成功,已經被服務器成功處理
數字3開頭的表示:需要客戶端采取進一步的操作才能完成請求
數字4開頭的表示:客戶端看起來可能發生了錯誤,妨礙了服務器的處理
數字5開頭的:表示服務器在處理請求的過程中有錯誤或者異常狀態發生,也有可能是服務器意識到以當前的軟硬件資源無法完成對請求的處理
請求/響應頭部
請求和響應頭部也是分析時常用到的。常用的請求頭部(部分):
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:服務器的一些相關信息
請求頭部和響應頭部是有對應關系的:例如
1.請求頭部的 Accept要和響應頭部的 Content-Type匹配,否則會報錯。
2.跨域請求時,請求頭部的 Origin要匹配響應頭部的 Access-Control-Allow-Origin,否則會報跨域錯誤。
3.在使用緩存時,請求頭部的 If-Modified-Since、 If-None-Match分別和響應頭部的 Last-Modified、 ETag對應。
更多的對應關系請讀者自行搜索。
請求/響應實體
做http請求時,除了頭部,還有消息實體,一般來說,請求實體中會將一些需要的參數都放入進入(用于post請求)。譬如實體中可以放參數的序列化形式( a=1&b=2這種),或者直接放表單對象( FormData對象,上傳時可以夾雜參數以及文件),等等。
而一般響應實體中,就是放服務端需要傳給客戶端的內容。一般現在的接口請求時,實體中就是對于的信息的json格式。
cookie以及優化
cookie是瀏覽器的一種本地存儲方式,一般用來幫助客戶端和服務端通信的,常用來進行身份校驗,結合服務端的session使用。
常用的場景如下:
用戶登陸后,服務端會生成一個session,session中有對于用戶的信息(如用戶名、密碼等),然后會有一個sessionid(相當于是服務端的這個session對應的key),然后服務端在登錄頁面中寫入cookie,值就是:jsessionid=xxx,然后瀏覽器本地就有這個cookie了,以后訪問同域名下的頁面時,自動帶上cookie,自動檢驗,在有效時間內無需二次登陸。
一般來說,cookie是不允許存放敏感信息的(千萬不要明文存儲用戶名、密碼),因為非常不安全,如果一定要強行存儲,首先,一定要在cookie中設置 httponly(這樣就無法通過js操作了)。
另外,由于在同域名的資源請求時,瀏覽器會默認帶上本地的cookie,針對這種情況,在某些場景下是需要優化的。
例如以下場景:
客戶端在域名A下有cookie(這個可以是登陸時由服務端寫入的) 然后在域名A下有一個頁面,頁面中有很多依賴的靜態資源(都是域名A的,譬如有20個靜態資源) 此時就有一個問題,頁面加載,請求這些靜態資源時,瀏覽器會默認帶上cookie 也就是說,這20個靜態資源的http請求,每一個都得帶上cookie,而實際上靜態資源并不需要cookie驗證 此時就造成了較為嚴重的浪費,而且也降低了訪問速度(因為內容更多了)
當然了,針對這種場景,是有優化方案的(多域名拆分)。具體做法就是:
將靜態資源分組,分別放到不同的子域名下 而子域名請求時,是不會帶上父級域名的cookie的,所以就避免了浪費
說到了多域名拆分,這里再提一個問題,那就是:
在移動端,如果請求的域名數過多,會降低請求速度(因為域名整套解析流程是很耗費時間的,而且移動端一般帶寬都比不上pc)
此時就需要用到一種優化方案: dns-prefetch(讓瀏覽器空閑時提前解析dns域名,不過也請合理使用,勿濫用)
gzip壓縮
首先,明確 gzip是一種壓縮格式,需要瀏覽器支持才有效(不過一般現在瀏覽器都支持),而且gzip壓縮效率很好(高達70%左右)。然后gzip一般是由 apache、 tomcat等web服務器開啟。
當然服務器除了gzip外,也還會有其它壓縮格式(如deflate,沒有gzip高效,且不流行),所以一般只需要在服務器上開啟了gzip壓縮,然后之后的請求就都是基于gzip壓縮格式的,非常方便。
長連接與短連接
首先看 tcp/ip層面的定義:
長連接:一個tcp/ip連接上可以連續發送多個數據包,在tcp連接保持期間,如果沒有數據包發送,需要雙方發檢測包以維持此連接,一般需要自己做在線維持(類似于心跳包) 短連接:通信雙方有數據交互時,就建立一個tcp連接,數據發送完成后,則斷開此tcp連接
然后在http層面:
http1.0中,默認使用的是短連接,也就是說,瀏覽器沒進行一次http操作,就建立一次連接,任務結束就中斷連接,譬如每一個靜態資源請求時都是一個多帶帶的連接 http1.1起,默認使用長連接,使用長連接會有這一行 Connection:keep-alive,在長連接的情況下,當一個網頁打開完成后,客戶端和服務端之間用于傳輸http的tcp連接不會關閉,如果客戶端再次訪問這個服務器的頁面,會繼續使用這一條已經建立的連接
注意: keep-alive不會永遠保持,它有一個持續時間,一般在服務器中配置(如apache),另外長連接需要客戶端和服務器都支持時才有效。
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就是安全版本的http,譬如一些支付等操作基本都是基于https的,因為http請求的安全系數太低了。
簡單來看,https與http的區別就是: 在請求前,會建立ssl鏈接,確保接下來的通信都是加密的,無法被輕易截取分析
一般來說,如果要將網站升級成https,需要后端支持(后端需要申請證書等),然后https的開銷也比http要大(因為需要額外建立安全鏈接以及加密等),所以一般來說http2.0配合https的體驗更佳(因為http2.0更快了)
一般來說,主要關注的就是SSL/TLS的握手流程:
1.瀏覽器請求建立SSL鏈接,并向服務端發送一個隨機數–Client random和客戶端支持的加密方法,比如RSA加密,此時是明文傳輸。 2.服務端從中選出一組加密算法與Hash算法,回復一個隨機數–Server random,并將自己的身份信息以證書的形式發回給瀏覽器 (證書里包含了網站地址,非對稱加密的公鑰,以及證書頒發機構等信息) 3.瀏覽器收到服務端的證書后 驗證證書的合法性(頒發機構是否合法,證書中包含的網址是否和正在訪問的一樣),如果證書信任,則瀏覽器會顯示一個小鎖頭,否則會有提示 用戶接收證書后(不管信不信任),瀏覽會生產新的隨機數–Premaster secret,然后證書中的公鑰以及指定的加密方法加密 Premastersecret,發送給服務器。 利用Client random、Server random和Premaster secret通過一定的算法生成HTTP鏈接數據傳輸的對稱加密key- session key 使用約定好的HASH算法計算握手消息,并使用生成的 session key對消息進行加密,最后將之前生成的所有信息發送給服務端。 4.服務端收到瀏覽器的回復 利用已知的加解密方式與自己的私鑰進行解密,獲取 Premastersecret 和瀏覽器相同規則生成 session key 使用 session key解密瀏覽器發來的握手消息,并驗證Hash是否與瀏覽器發來的一致 使用 session key加密一段握手消息,發送給瀏覽器 5.瀏覽器解密并計算握手消息的HASH,如果與服務端發來的HASH一致,此時握手過程結束,
之后所有的https通信數據將由之前瀏覽器生成的 session key并利用對稱加密算法進行加密。
http的緩存
前后端的http交互中,使用緩存能很大程度上的提升效率,而且基本上對性能有要求的前端項目都是必用緩存的。
強緩存與弱緩存
緩存可以簡單的劃分成兩種類型: 強緩存( 200fromcache)與 協商緩存( 304)
區別如下:
強緩存( 200fromcache)時,瀏覽器如果判斷本地緩存未過期,就直接使用,無需發起http請求 協商緩存( 304)時,瀏覽器會向服務端發起http請求,然后服務端告訴瀏覽器文件未改變,讓瀏覽器使用本地緩存
對于協商緩存,使用 Ctrl+F5強制刷新可以使得緩存無效。但是對于強緩存,在未過期時,必須更新資源路徑才能發起新的請求(更改了路徑相當于是另一個資源了,這也是前端工程化中常用到的技巧)。
緩存頭部簡述
上述提到了強緩存和協商緩存,那它們是怎么區分的呢?答案是通過不同的http頭部控制。
緩存中常用的幾個頭部:
If-None-Match/E-tag If-Modified-Since/Last-Modified Cache-Control/Max-Age Prama/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時期的。
頭部的區別
首先明確,http的發展是從http1.0到http1.1,而在http1.1中,出了一些新內容,彌補了http1.0的不足。
http1.0中的緩存控制:
Pragma:嚴格來說,它不屬于專門的緩存控制頭部,但是它設置 no-cache時可以讓本地強緩存失效(屬于編譯控制,來實現特定的指令,主要是因為兼容http1.0,所以以前又被大量應用) Expires:服務端配置的,屬于強緩存,用來控制在規定的時間之前,瀏覽器不會發出請求,而是直接使用本地緩存,注意,Expires一般對應服務器端時間,如 Expires:Fri,30Oct199814: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更精確,它是類似于指紋一樣的東西,基于 FileEtagINodeMtimeSize生成,也就是說,只要文件變,指紋就會變,而且沒有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。6. 瀏覽器接收到http數據包后的解析流程
渲染流程大致如下:
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樹。
Bytes → characters → tokens → nodes → DOM
假設有下面這樣一個代碼
Critical Path Helloweb performance students!
瀏覽器的處理如下:
列舉其中的一些重點過程:
Conversion轉換:瀏覽器將獲得的HTML內容(Bytes)基于他的編碼轉換為單個字符 Tokenizing分詞:瀏覽器按照HTML規范標準將這些字符轉換為不同的標記token。每個token都有自己獨特的含義以及規則集 Lexing詞法分析:分詞的結果是得到一堆的token,此時把他們轉換為對象,這些對象分別定義他們的屬性和規則 DOM構建:因為HTML標記定義的就是不同標簽之間的關系,這個關系就像是一個樹形結構一樣。例如:body對象的父節點就是HTML對象,然后段略p對象的父節點就是body對象
最后的DOM樹如下:
生成CSS規則
Bytes → characters → tokens → nodes → CSSOM
有如下css代碼:
body { font-size: 16px } p { font-weight: bold } span { color: red} p span { display: none } img { float: right }
cssom樹:
當DOM樹和CSSOM都有了后,就要開始構建渲染樹了。
然后從渲染樹開始生成我們看到的html頁面。
在這個過程中又一個小問題,重新構建和渲染頁面:
重新構建,也稱為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
回流一定伴隨著重繪,重繪卻可以多帶帶出現。所以一般會有一些優化方案,如:
1.減少逐項更改樣式,最好一次性更改style,或者將樣式定義為class并一次性更新 2.避免循環操作dom,創建一個documentFragment或div,在它上面應用所有DOM操作,最后再把它添加到window.document 3.避免多次讀取offset等屬性。無法避免則將它們緩存到變量 4.將復雜的元素絕對定位或固定定位,使得它脫離文檔流,否則回流代價會很高
注意:改變字體大小會引發回流
var s = document.body.style; s.padding = "2px"; // 回流+重繪 s.border = "1px solid red"; // 再一次 回流+重繪 s.color = "blue"; // 再一次重繪 s.backgroundColor = "#ccc"; // 再一次 重繪 s.fontSize = "14px"; // 再一次 回流+重繪 // 添加node,再一次 回流+重繪 document.body.appendChild(document.createTextNode("abc!"));
資源外鏈的下載
上面介紹了html解析,渲染流程。但實際上,在解析html時,會遇到一些資源連接,此時就需要進行多帶帶處理了。簡單起見,這里將遇到的靜態資源分為一下幾大類(未列舉所有):
CSS樣式資源 JS腳本資源 img圖片類資源 遇到外鏈時的處理
當遇到上述的外鏈時,會多帶帶開啟一個下載線程去下載資源(http1.1中是每一個資源的下載都要開啟一個http請求,對應一個tcp/ip鏈接)。
遇到CSS樣式資源
CSS資源的處理有幾個特點:
CSS下載時異步,不會阻塞瀏覽器構建DOM樹 但是會阻塞渲染,也就是在構建render時,會等到css下載解析完畢后才進行(這點與瀏覽器優化有關,防止css規則不斷改變,避免了重復的構建) 有例外, media query聲明的CSS是不會阻塞渲染的
遇到JS腳本資源
JS腳本資源的處理有幾個特點:
阻塞瀏覽器的解析,也就是說發現一個外鏈腳本時,需等待腳本下載完成并執行后才會繼續解析HTML 瀏覽器的優化,一般現代瀏覽器有優化,在腳本阻塞時,也會繼續下載其它資源(當然有并發上限),但是雖然腳本可以并行下載,解析過程仍然是阻塞的,也就是說必須這個腳本執行完畢后才會接下來的解析,并行下載只是一種優化而已 defer與async,普通的腳本是會阻塞瀏覽器解析的,但是可以加上defer或async屬性,這樣腳本就變成異步了,可以等到解析完畢后再執行 注意,defer和async是有區別的: defer是延遲執行,而async是異步執行。
簡單的說(不展開):
async是異步執行,異步下載完畢后就會執行,不確保執行順序,一定在 onload前,但不確定在 DOMContentLoaded事件的前或后 defer是延遲執行,在瀏覽器看起來的效果像是將腳本放在了 body后面一樣(雖然按規范應該是在 DOMContentLoaded事件前,但實際上不同瀏覽器的優化效果不一樣,也有可能在它后面)
遇到img圖片類資源
遇到圖片等資源時,直接就是異步下載,不會阻塞解析,下載完畢后直接用圖片替換原有src的地方。
loaded和domcontentloaded
簡單的對比:
DOMContentLoaded 事件觸發時,僅當DOM加載完成,不包括樣式表,圖片(譬如如果有async加載的腳本就不一定完成) load 事件觸發時,頁面上所有的DOM,樣式表,腳本,圖片都已經加載完成了7. CSS的可視化格式模型
html元素按什么規則渲染,接下來提到的內容來揭曉
CSS中規定每一個元素都有自己的盒子模型(相當于規定了這個元素如何顯示) 然后可視化格式模型則是把這些盒子按照規則擺放到頁面上,也就是如何布局 換句話說,盒子模型規定了怎么在頁面里擺放盒子,盒子的相互作用等等
說到底: CSS的可視化格式模型就是規定了瀏覽器在頁面中如何處理文檔樹。
關鍵字:
包含塊(Containing Block) 控制框(Controlling Box) BFC(Block Formatting Context) IFC(Inline Formatting Context) 定位體系 浮動
CSS有三種定位機制: 普通流, 浮動, 絕對定位
包含塊(Containing Block)
一個元素的box的定位和尺寸,會與某一矩形框有關,這個框就稱之為包含塊。元素會為它的子孫元素創建包含塊,但是,并不是說元素的包含塊就是它的父元素,元素的包含塊與它的祖先元素的樣式等有關系。
比如:
根元素是最頂端的元素,它沒有父節點,它的包含塊就是初始包含塊 static和relative的包含塊由它最近的塊級、單元格或者行內塊祖先元素的內容框(content)創建 fixed的包含塊是當前可視窗口 absolute的包含塊由它最近的position 屬性為 absolute、 relative或者 fixed的祖先元素創建 如果其祖先元素是行內元素,則包含塊取決于其祖先元素的 direction特性 如果祖先元素不是行內元素,那么包含塊的區域應該是祖先元素的內邊距邊界
控制框(Controlling Box)
塊級元素和塊框以及行內元素和行框的相關概念
塊框:
塊級元素會生成一個塊框( BlockBox),塊框會占據一整行,用來包含子box和生成的內容 塊框同時也是一個塊包含框( ContainingBox),里面要么只包含塊框,要么只包含行內框(不能混雜),如果塊框內部有塊級元素也有行內元素,那么行內元素會被匿名塊框包圍
如果一個塊框在其中包含另外一個塊框,那么我們強迫它只能包含塊框,因此其它文本內容生成出來的都是匿名塊框(而不是匿名行內框)。
行內框:
一個行內元素生成一個行內框 行內元素能排在一行,允許左右有其它元素
display的幾個屬性也可以影響不同框的生成:
block,元素生成一個塊框 inline,元素產生一個或多個的行內框 inline-block,元素產生一個行內級塊框,行內塊框的內部會被當作塊塊來格式化,而此元素本身會被當作行內級框來格式化(這也是為什么會產生 BFC) none,不生成框,不再格式化結構中,當然了,另一個 visibility:hidden則會產生一個不可見的框
BFC(Block Formatting Context)
FC即格式上下文,它定義框內部的元素渲染規則,比較抽象,比如:
FC像是一個大箱子,里面裝有很多元素 箱子可以隔開里面的元素和外面的元素(所以外部并不會影響FC內部的渲染) 內部的規則可以是:如何定位,寬高計算,margin折疊等等 不同類型的框參與的FC類型不同,譬如塊級框對應BFC,行內框對應IFC。
注意,并不是說所有的框都會產生FC,而是符合特定條件才會產生,只有產生了對應的FC后才會應用對應渲染規則。
BFC規則:
在塊格式化上下文中,每一個元素左外邊與包含塊的左邊相接觸(對于從右到左的格式化,右外邊接觸右邊),即使存在浮動也是如此(所以浮動元素正常會直接貼近它的包含塊的左邊,與普通元素重合),除非這個元素也創建了一個新的BFC。
總結幾點BFC特點:
內部 box在垂直方向,一個接一個的放置 box的垂直方向由 margin決定,屬于同一個BFC的兩個box間的margin會重疊 BFC區域不會與 floatbox重疊(可用于排版) BFC就是頁面上的一個隔離的獨立容器,容器里面的子元素不會影響到外面的元素。反之也如此 計算BFC的高度時,浮動元素也參與計算(不會浮動坍塌)
如何觸發BFC?
根元素 float屬性不為 none position為 absolute或 fixed display為 inline-block, flex, inline-flex, table, table-cell, table-caption overflow不為 visible
這里提下, display:table,它本身不產生BFC,但是它會產生匿名框(包含 display:table-cell的框)。
IFC(Inline Formatting Context)
IFC即行內框產生的格式上下文。
IFC規則
在行內格式化上下文中,框一個接一個地水平排列,起點是包含塊的頂部。水平方向上的 margin,border 和 padding 在框之間得到保留,框在垂直方向上可以以不同的方式對齊:它們的頂部或底部對齊,或根據其中文字的基線對齊。
行框
包含那些框的長方形區域,會形成一行,叫做行框。行框的寬度由它的包含塊和其中的浮動元素決定,高度的確定由行高度計算規則決定。
行框的規則:
如果幾個行內框在水平方向無法放入一個行框內,它們可以分配在兩個或多個垂直堆疊的行框中(即行內框的分割) 行框在堆疊時沒有垂直方向上的分割且永不重疊 行框的高度總是足夠容納所包含的所有框。不過,它可能高于它包含的最高的框(例如,框對齊會引起基線對齊) 行框的左邊接觸到其包含塊的左邊,右邊接觸到其包含塊的右邊
結合補充下IFC規則
浮動元素可能會處于包含塊邊緣和行框邊緣之間,盡管在相同的行內格式化上下文中的行框通常擁有相同的寬度(包含塊的寬度),它們可能會因浮動元素縮短了可用寬度,而在寬度上發生變化。
同一行內格式化上下文中的行框通常高度不一樣(如,一行包含了一個高的圖形,而其它行只包含文本),當一行中行內框寬度的總和小于包含它們的行框的寬,它們在水平方向上的對齊,取決于 text-align 特性。空的行內框應該被忽略。
即不包含文本,保留空白符,margin/padding/border非0的行內元素,以及其他常規流中的內容(比如,圖片,inline blocks 和 inline tables),并且不是以換行結束的行框,必須被當作零高度行框對待。
總結:
行內元素總是會應用IFC渲染規則 行內元素會應用IFC規則渲染,譬如 text-align可以用來居中等 塊框內部,對于文本這類的匿名元素,會產生匿名行框包圍,而行框內部就應用IFC渲染規則 行內框內部,對于那些行內元素,一樣應用IFC渲染規則 另外, inline-block,會在元素外層產生IFC(所以這個元素是可以通過 text-align水平居中的),當然,它內部則按照BFC規則渲染 相比BFC規則來說,IFC可能更加抽象(因為沒有那么條理清晰的規則和觸發條件),但總的來說,它就是行內元素自身如何顯示以及在框內如何擺放的渲染規則,這樣描述應該更容易理解。
關于css的一些別的規則,大家可以去搜搜:
如常規流,浮動,絕對定位等區別 如浮動元素不包含在常規流中 如相對定位,絕對定位, Fixed定位等區別 如 z-index的分層顯示機制等8. JS引擎解析過程
這個部分的內容請參考這兒:JS引擎解析過程
直接略過了
跨域
為什么會跨域:
在瀏覽器同源策略限制下,向不同源(不同協議、不同域名或者不同端口)發送XHR請求,瀏覽器認為該請求不受信任,禁止請求,具體表現為請求后不正常響應
舉個栗子:
那要怎么搞呢?網上的解決辦法也很多,這兒列一些:
1.jsonp 2.cors 3.document.domain 4.POSTmessage
想深入看的可以瀏覽一下這個 跨域的常用解決方式
web安全
這個東西,我們在面試的時候肯定會被問到xss攻擊的問題,大家自行搜索把,這個問題的解決方案也超級多,官方文檔也介紹很詳細,這兒就不整了
參考:參考文章
有大佬也總結過了很詳細的內容,我照搬了點兒內容,勿噴,只是學習一下。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/114285.html
摘要:通用頭部這也是開發人員見過的最多的信息,包括如下請求的服務器地址請求方式請求的返回狀態碼,如代表成功請求的遠程服務器地址會轉為譬如,在跨域拒絕時,可能是為,狀態碼為等當然,實際上可能的組合有很多。 面試中經常會被問到這個問題吧,唉,我最開始被問到的時候也就能大概說一些流程。被問得多了,自己就想去找找這個問題的全面回答,于是乎搜了很多資料和網上的文章,根據那些文章寫一個總結。 寫得不好...
摘要:通用頭部這也是開發人員見過的最多的信息,包括如下請求的服務器地址請求方式請求的返回狀態碼,如代表成功請求的遠程服務器地址會轉為譬如,在跨域拒絕時,可能是為,狀態碼為等當然,實際上可能的組合有很多。 面試中經常會被問到這個問題吧,唉,我最開始被問到的時候也就能大概說一些流程。被問得多了,自己就想去找找這個問題的全面回答,于是乎搜了很多資料和網上的文章,根據那些文章寫一個總結。 寫得不好...
摘要:勤學學習效率與效果取決于執行力。這一步學習的正確姿勢是在實踐操作中發掘問題,然后帶著問題找答案。拆分任務將目標分解成具體可執行的學習任務。勤學強大的執行力是學習的根本保障。分享復述檢驗學習成果,提高學習效果的最好方法。 showImg(https://segmentfault.com/img/bVbcPGZ?w=256&h=256); 前段時間和大家一起分享了一篇關于學習方法內容《大牛...
摘要:行勝于言,理論結合實踐才是王道,所以本文我將基于前面的學習方法,分享我是如何學習微信小程序的。第二個目標則需要學習小程序的插件相關接口調用,以及蟬知建站系統這邊的微信模塊代碼。 前段時間和大家一起分享了一篇關于學習方法內容《大牛與搬運工的差距——學習方法的力量》。我們將學習過程分成八步,并借鑒了敏捷開發的迭代思想,以達到自我迭代學習的效果。行勝于言,理論結合實踐才是王道,所以本文我將基...
摘要:梳理之后,目標就會被分解成一個個需要完成的具體任務。勤學學習效率與效果取決于執行力。這種選手即便幫他解決了問題,他也學不到東西。拆分任務將目標分解成具體可執行的學習任務。搜集知識資源查閱官方文檔購買書籍搜集網絡干貨文章。 前段時間和大家一起分享了一篇關于學習方法內容《大牛與搬運工的差距——學習方法的力量》。我們將學習過程分成八步,并借鑒了敏捷開發的迭代思想,以達到自我迭代學習的效果。行...
閱讀 732·2021-11-23 09:51
閱讀 2430·2021-10-11 11:10
閱讀 1298·2021-09-23 11:21
閱讀 1089·2021-09-10 10:50
閱讀 882·2019-08-30 15:54
閱讀 3326·2019-08-30 15:53
閱讀 3287·2019-08-30 15:53
閱讀 3186·2019-08-29 17:23