解析URL
輸入URL后,會進行解析(URL的本質就是統一資源定位符)
URL一般包括幾大部分:
protocol,協議頭,譬如有http,ftp等
host,主機域名或IP地址
port,端口號
path,目錄路徑
query,即查詢參數
fragment,即#后的hash值,一般用來定位到某個位置
網絡請求都是單獨的線程
每次網絡請求時都需要開辟單獨的線程進行,譬如如果URL解析到http協議,就會新建一個網絡線程去處理資源下載。
因此瀏覽器會根據解析出得協議,開辟一個網絡線程,前往請求資源(這里,暫時理解為是瀏覽器內核開辟的,如有錯誤,后續修復)。
更多
由于篇幅關系,這里就大概介紹一個主干流程,關于瀏覽器的進程機制,更多可以參考以前總結的一篇文章(因為內容實在過多,里面包括JS運行機制,進程線程的詳解)。
從瀏覽器多進程到JS單線程,JS運行機制最全面的一次梳理:https://segmentfault.com/a/1190000012925872。
開啟網絡線程到發出一個完整的http請求
這一部分主要內容包括:dns查詢,tcp/ip請求構建,五層因特網協議棧等等。
仍然是先梳理主干,有些詳細的過程不展開(因為展開的話內容過多)。
DNS查詢得到IP
如果輸入的是域名,需要進行dns解析成IP,大致流程:
如果瀏覽器有緩存,直接使用瀏覽器緩存,否則使用本機緩存,再沒有的話就是用host
如果本地沒有,就向dns域名服務器查詢(當然,中間可能還會經過路由,也有緩存等),查詢到對應的IP
注意,域名查詢時有可能是經過了CDN調度器的(如果有cdn存儲功能的話)。
而且,需要知道dns解析是很耗時的,因此如果解析域名過多,會讓首屏加載變得過慢,可以考慮dns-prefetch優化。
這一塊可以深入展開,具體請去網上搜索,這里就不占篇幅了(網上可以看到很詳細的解答)。
tcp/ip請求
http的本質就是tcp/ip請求。
需要了解3次握手規則建立連接以及斷開連接時的四次揮手。
tcp將http長報文劃分為短報文,通過三次握手與服務端建立連接,進行可靠傳輸。
三次握手的步驟(抽象派)
客戶端:hello,你是server么?
服務端:hello,我是server,你是client么
客戶端:yes,我是client
建立連接成功后,接下來就正式傳輸數據。
然后,待到斷開連接時,需要進行四次揮手(因為是全雙工的,所以需要四次揮手)。
四次揮手的步驟(抽象派)
主動方:我已經關閉了向你那邊的主動通道了,只能被動接收了
被動方:收到通道關閉的信息
被動方:那我也告訴你,我這邊向你的主動通道也關閉了
主動方:最后收到數據,之后雙方無法通信
tcp/ip的并發限制
瀏覽器對同一域名下并發的tcp連接是有限制的(2-10個不等)。
而且在http1.0中往往一個資源下載就需要對應一個tcp/ip請求。
所以針對這個瓶頸,又出現了很多的資源優化方案。
get和post的區別
get和post雖然本質都是tcp/ip,但兩者除了在http層面外,在tcp/ip層面也有區別。
get會產生一個tcp數據包,post兩個。
具體就是:
get請求時,瀏覽器會把headers和data一起發送出去,服務器響應200(返回數據),
post請求時,瀏覽器先發送headers,服務器響應100continue,瀏覽器再發送data,服務器響應200(返回數據)。
再說一點,這里的區別是specification(規范)層面,而不是implementation(對規范的實現)
五層因特網協議棧
其實這個概念挺難記全的,記不全沒關系,但是要有一個整體概念。
其實就是一個概念:從客戶端發出http請求到服務器接收,中間會經過一系列的流程。
簡括就是:從應用層的發送http請求,到傳輸層通過三次握手建立tcp/ip連接,再到網絡層的ip尋址,再到數據鏈路層的封裝成幀,最后到物理層的利用物理介質傳輸。
當然,服務端的接收就是反過來的步驟。
五層因特爾協議棧其實就是:
1.應用層(dns,http) DNS解析成IP并發送http請求
2.傳輸層(tcp,udp) 建立tcp連接(三次握手)
3.網絡層(IP,ARP) IP尋址
4.數據鏈路層(PPP) 封裝成幀
5.物理層(利用物理介質傳輸比特流) 物理傳輸(然后傳輸的時候通過雙絞線,電磁波等各種介質)
當然,其實也有一個完整的OSI七層框架,與之相比,多了會話層、表示層。
OSI七層框架:物理層、數據鏈路層、網絡層、傳輸層、會話層、表示層、應用層。
表示層:主要處理兩個通信系統中交換信息的表示方式,包括數據格式交換,數據加密與解密,數據壓縮與終端類型轉換等
會話層:它具體管理不同用戶和進程之間的對話,如控制登陸和注銷過程
從服務器接收到請求到對應后臺接收到請求
服務端在接收到請求時,內部會進行很多的處理。這里由于不是專業的后端分析,所以只是簡單的介紹下,不深入。
負載均衡
對于大型的項目,由于并發訪問量很大,所以往往一臺服務器是吃不消的,所以一般會有若干臺服務器組成一個集群,然后配合反向代理實現負載均衡。
當然了,負載均衡不止這一種實現方式,這里不深入...
簡單的說:用戶發起的請求都指向調度服務器(反向代理服務器,譬如安裝了nginx控制負載均衡),然后調度服務器根據實際的調度算法,分配不同的請求給對應集群中的服務器執行,然后調度器等待實際服務器的HTTP響應,并將它反饋給用戶。
后臺的處理
一般后臺都是部署到容器中的,所以一般為:
先是容器接受到請求(如tomcat容器)
然后對應容器中的后臺程序接收到請求(如java程序)
然后就是后臺會有自己的統一處理,處理完后響應響應結果
概括下:
一般有的后端是有統一的驗證的,如安全攔截,跨域驗證
如果這一步不符合規則,就直接返回了相應的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 方法。
這里面最常用到的就是狀態碼,很多時候都是通過狀態碼來判斷,如(列舉幾個最常見的):
200——表明該請求被成功地完成,所請求的資源發送回客戶端
304——自從上次請求后,請求的網頁未修改過,請客戶端使用本地緩存
400——客戶端請求有錯(譬如可以是安全模塊攔截)
401——請求未經授權
403——禁止訪問(譬如可以是未登錄時禁止)
404——資源未找到
500——服務器內部錯誤
503——服務不可用
...
再列舉下大致不同范圍狀態的意義:
1xx——指示信息,表示請求已接收,繼續處理
2xx——成功,表示請求已被成功接收、理解、接受
3xx——重定向,要完成請求必須進行更進一步的操作
4xx——客戶端錯誤,請求有語法錯誤或請求無法實現
5xx——服務器端錯誤,服務器未能實現合法的請求
總之,當請求出錯時,狀態碼能幫助快速定位問題,完整版本的狀態可以自行去互聯網搜索。
請求/響應頭部
請求和響應頭部也是分析時常用到的。常用的請求頭部(部分):
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:服務器的一些相關信息
一般來說,請求頭部和響應頭部是匹配分析的。
譬如,請求頭部的Accept要和響應頭部的Content-Type匹配,否則會報錯。
譬如,跨域請求時,請求頭部的Origin要匹配響應頭部的Access-Control-Allow-Origin,否則會報跨域錯誤。
譬如,在使用緩存時,請求頭部的If-Modified-Since、If-None-Match分別和響應頭部的Last-Modified、ETag對應。
還有很多的分析方法,這里不一一贅述。
請求/響應實體
http請求時,除了頭部,還有消息實體,一般來說,請求實體中會將一些需要的參數都放入進入(用于post請求)。譬如實體中可以放參數的序列化形式(a=1&b=2這種),或者直接放表單對象(FormData對象,上傳時可以夾雜參數以及文件),等等。
而一般響應實體中,就是放服務端需要傳給客戶端的內容。一般現在的接口請求時,實體中就是對于的信息的json格式,而像頁面請求這種,里面就是直接放了一個html字符串,然后瀏覽器自己解析并渲染。
CRLF
CRLF(Carriage-Return Line-Feed),意思是回車換行,一般作為分隔符存在。
請求頭和實體消息之間有一個CRLF分隔,響應頭部和響應實體之間用一個CRLF分隔。
一般來說(分隔符類別):
CRLF->Windows-style
LF->Unix Style
CR->Mac Style
如下圖是對某請求的http報文結構的簡要分析:
cookie以及優化
cookie是瀏覽器的一種本地存儲方式,一般用來幫助客戶端和服務端通信的,常用來進行身份校驗,結合服務端的session使用。
場景如下(簡述):
在登陸頁面,用戶登陸了
此時,服務端會生成一個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驗證
此時就造成了較為嚴重的浪費,而且也降低了訪問速度(因為內容更多了)
當然了,針對這種場景,是有優化方案的(多域名拆分)。具體做法就是:
將靜態資源分組,分別放到不同的子域名下
而子域名請求時,是不會帶上父級域名的cookie的,所以就避免了浪費
說到了多域名拆分,這里再提一個問題,那就是:
在移動端,如果請求的域名數過多,會降低請求速度(因為域名整套解析流程是很耗費時間的,而且移動端一般帶寬都比不上pc)
此時就需要用到一種優化方案:dns-prefetch(讓瀏覽器空閑時提前解析dns域名,不過也請合理使用,勿濫用)
關于cookie的交互,可以看下圖總結:
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的握手流程,如下(簡述):
瀏覽器請求建立SSL鏈接,并向服務端發送一個隨機數–Client random和客戶端支持的加密方法,比如RSA加密,此時是明文傳輸。
服務端從中選出一組加密算法與Hash算法,回復一個隨機數–Server random,并將自己的身份信息以證書的形式發回給瀏覽器 (證書里包含了網站地址,非對稱加密的公鑰,以及證書頒發機構等信息)
瀏覽器收到服務端的證書后
驗證證書的合法性(頒發機構是否合法,證書中包含的網址是否和正在訪問的一樣),如果證書信任,則瀏覽器會顯示一個小鎖頭,否則會有提示
用戶接收證書后(不管信不信任),瀏覽會生產新的隨機數–Premaster secret,然后證書中的公鑰以及指定的加密方法加密Premastersecret,發送給服務器。
利用Client random、Server random和Premaster secret通過一定的算法生成HTTP鏈接數據傳輸的對稱加密key-session key
使用約定好的HASH算法計算握手消息,并使用生成的session key對消息進行加密,最后將之前生成的所有信息發送給服務端。
服務端收到瀏覽器的回復
利用已知的加解密方式與自己的私鑰進行解密,獲取Premastersecret
和瀏覽器相同規則生成session key
使用session key解密瀏覽器發來的握手消息,并驗證Hash是否與瀏覽器發來的一致
使用session key加密一段握手消息,發送給瀏覽器
瀏覽器解密并計算握手消息的HASH,如果與服務端發來的HASH一致,此時握手過程結束,
之后所有的https通信數據將由之前瀏覽器生成的session key并利用對稱加密算法進行加密。
這里放一張圖(來源:阮一峰-圖解SSL/TLS協議):
單獨拎出來的緩存問題,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、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,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。
各大緩存頭部的整體關系如下圖:
解析頁面流程
前面有提到http交互,那么接下來就是瀏覽器獲取到html,然后解析,渲染。
這部分很多都參考了網上資源,特別是圖片,參考了來源中的文章。
流程簡述
瀏覽器內核拿到內容后,渲染步驟大致可以分為以下幾步:
解析HTML,構建DOM樹
解析CSS,生成CSS規則樹
合并DOM樹和CSS規則,生成render樹
布局render樹(Layout/reflow),負責各元素尺寸、位置的計算
繪制render樹(paint),繪制頁面像素信息
瀏覽器會將各層的信息發送給GPU,GPU會將各層合成(composite),顯示在屏幕上
如下圖:
HTML解析,構建DOM
整個渲染步驟中,HTML解析是第一步。簡單的理解,這一步的流程是這樣的:瀏覽器解析HTML,構建DOM樹。但實際上,在分析整體構建時,卻不能一筆帶過,得稍微展開。
解析HTML到構建出DOM當然過程可以簡述如下:
Bytes → characters → tokens → nodes → DOM
譬如假設有這樣一個HTML頁面:(以下部分的內容出自參考來源,修改了下格式)
? ?
? ?
? ?Critical Path
? ?
Hello web performance students!
? ?
瀏覽器的處理如下:
列舉其中的一些重點過程:
Conversion轉換:瀏覽器將獲得的HTML內容(Bytes)基于他的編碼轉換為單個字符
Tokenizing分詞:瀏覽器按照HTML規范標準將這些字符轉換為不同的標記token。每個token都有自己獨特的含義以及規則集
Lexing詞法分析:分詞的結果是得到一堆的token,此時把他們轉換為對象,這些對象分別定義他們的屬性和規則
DOM構建:因為HTML標記定義的就是不同標簽之間的關系,這個關系就像是一個樹形結構一樣。例如:body對象的父節點就是HTML對象,然后段略p對象的父節點就是body對象
最后的DOM樹如下:
生成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等。
整體來說可以看圖:
渲染
有了render樹,接下來就是開始渲染,基本流程如下:
圖中重要的四個步驟就是:
計算CSS樣式
構建渲染樹
布局,主要定位坐標和大小,是否換行,各種position overflow z-index屬性
繪制,將圖像繪制出來
然后,圖中的線與箭頭代表通過js動態修改了DOM或CSS,導致了重新布局(Layout)或渲染(Repaint)。
這里Layout和Repaint的概念是有區別的:
Layout,也稱為Reflow,即回流。一般意味著元素的內容、結構、位置或尺寸發生了變化,需要重新計算樣式和渲染樹
Repaint,即重繪。意味著元素發生的改變只是影響了元素的一些外觀之類的時候(例如,背景色,邊框顏色,文字顏色等),此時只需要應用新樣式繪制這個元素就可以了
回流的成本開銷要高于重繪,而且一個節點的回流往往回導致子節點以及同級節點的回流,所以優化方案中一般都包括,盡量避免回流。
什么會引起回流?
1.頁面渲染初始化
2.DOM結構改變,比如刪除了某個節點
3.render樹變化,比如減少了padding
4.窗口resize
5.最復雜的一種:獲取某些屬性,引發回流。
很多瀏覽器會對回流做優化,會等到數量足夠時做一次批處理回流,但是除了render樹的直接變化,當獲取一些屬性時,瀏覽器為了獲得正確的值也會觸發回流,這樣使得瀏覽器優化無效,包括:
offset(Top/Left/Width/Height)
scroll(Top/Left/Width/Height)
cilent(Top/Left/Width/Height)
width,height
調用了getComputedStyle()或者IE的currentStyle
回流一定伴隨著重繪,重繪卻可以單獨出現。所以一般會有一些優化方案,如:
減少逐項更改樣式,最好一次性更改style,或者將樣式定義為class并一次性更新
避免循環操作dom,創建一個documentFragment或div,在它上面應用所有DOM操作,最后再把它添加到window.document
避免多次讀取offset等屬性。無法避免則將它們緩存到變量
將復雜的元素絕對定位或固定定位,使得它脫離文檔流,否則回流代價會很高
注意:改變字體大小會引發回流
再來看一個示例:
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!'));
簡單層與復合層
上述中的渲染中止步于繪制,但實際上繪制這一步也沒有這么簡單,它可以結合復合層和簡單層的概念來講。這里不展開,進簡單介紹下:
可以認為默認只有一個復合圖層,所有的DOM節點都是在這個復合圖層下的
如果開啟了硬件加速功能,可以將某個節點變成復合圖層
復合圖層之間的繪制互不干擾,由GPU直接控制
而簡單圖層中,就算是absolute等布局,變化時不影響整體的回流,但是由于在同一個圖層中,仍然是會影響繪制的,因此做動畫時性能仍然很低。而復合層是獨立的,所以一般做動畫推薦使用硬件加速
更多參考:
普通圖層和復合圖層
Chrome中的調試
Chrome的開發者工具中,Performance中可以看到詳細的渲染過程:
資源外鏈的下載
上面介紹了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,樣式表,腳本,圖片都已經加載完成了
CSS的可視化格式模型
這一部分內容很多參考《精通CSS-高級Web標準解決方案》以及參考來源。
前面提到了整體的渲染概念,但實際上文檔樹中的元素是按什么渲染規則渲染的,是可以進一步展開的,此部分內容即:CSS的可視化格式模型。
先了解:
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),里面要么只包含塊框,要么只包含行內框(不能混雜),如果塊框內部有塊級元素也有行內元素,那么行內元素會被匿名塊框包圍
關于匿名塊框的生成,示例:
Some text
More text
div生成了一個塊框,包含了另一個塊框p以及文本內容Sometext,此時Sometext文本會被強制加到一個匿名的塊框里面,被div生成的塊框包含(其實這個就是IFC中提到的行框,包含這些行內框的這一行匿名塊形成的框,行框和行內框不同)。
換句話說:如果一個塊框在其中包含另外一個塊框,那么我們強迫它只能包含塊框,因此其它文本內容生成出來的都是匿名塊框(而不是匿名行內框)。
行內框
一個行內元素生成一個行內框
行內元素能排在一行,允許左右有其它元素
關于匿名行內框的生成,示例:
Some emphasized text
P元素生成一個塊框,其中有幾個行內框(如EM),以及文本Some,text,此時會專門為這些文本生成匿名行內框。
display屬性的影響
display的幾個屬性也可以影響不同框的生成:
block,元素生成一個塊框
inline,元素產生一個或多個的行內框
inline-block,元素產生一個行內級塊框,行內塊框的內部會被當作塊塊來格式化,而此元素本身會被當作行內級框來格式化(這也是為什么會產生BFC)
none,不生成框,不再格式化結構中,當然了,另一個visibility:hidden則會產生一個不可見的框
總結:
如果一個框里,有一個塊級元素,那么這個框里的內容都會被當作塊框來進行格式化,因為只要出現了塊級元素,就會將里面的內容分塊幾塊,每一塊獨占一行(出現行內可以用匿名塊框解決)
如果一個框里,沒有任何塊級元素,那么這個框里的內容會被當成行內框來格式化,因為里面的內容是按照順序成行的排列
BFC(Block Formatting Context)
FC(格式上下文)?
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的框),而這個匿名框產生BFC。更多請自行網上搜索。
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可能更加抽象(因為沒有那么條理清晰的規則和觸發條件),但總的來說,它就是行內元素自身如何顯示以及在框內如何擺放的渲染規則,這樣描述應該更容易理解。
其它
當然還有有一些其它內容:
譬如常規流,浮動,絕對定位等區別
譬如浮動元素不包含在常規流中
譬如相對定位,絕對定位,Fixed定位等區別
譬如z-index的分層顯示機制等
這里不一一展開,更多請參考:
http://bbs.csdn.net/topics/340204423
JS引擎解析過程
前面有提到遇到JS腳本時,會等到它的執行,實際上是需要引擎解析的,這里展開描述(介紹主干流程)。
JS的解釋階段
首先得明確:JS是解釋型語音,所以它無需提前編譯,而是由解釋器實時運行
引擎對JS的處理過程可以簡述如下:
讀取代碼,進行詞法分析(Lexical analysis),然后將代碼分解成詞元(token)
對詞元進行語法分析(parsing),然后將代碼整理成語法樹(syntax tree)
使用翻譯器(translator),將代碼轉為字節碼(bytecode)
使用字節碼解釋器(bytecode interpreter),將字節碼轉為機器碼
最終計算機執行的就是機器碼。為了提高運行速度,現代瀏覽器一般采用即時編譯(JIT-JustInTimecompiler)。即字節碼只在運行時編譯,用到哪一行就編譯哪一行,并且把編譯結果緩存(inlinecache),這樣整個程序的運行速度能得到顯著提升。而且,不同瀏覽器策略可能還不同,有的瀏覽器就省略了字節碼的翻譯步驟,直接轉為機器碼(如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):JavaScript核心(晉級高手必讀篇)(http://www.cnblogs.com/TomXu/archive/2012/01/12/2308594.html)。
解釋器解釋完語法規則后,就開始執行,然后整個執行流程中大致包含以下概念:
執行上下文,執行堆棧概念(如全局上下文,當前活動上下文)
VO(變量對象)和AO(活動對象)
作用域鏈
this機制等
這些概念如果深入講解的話內容過多,因此這里僅提及部分特性。
執行上下文簡單解釋
JS有執行上下文)
瀏覽器首次載入腳本,它將創建全局執行上下文,并壓入執行棧棧頂(不可被彈出)
然后每進入其它作用域就創建對應的執行上下文并把它壓入執行棧的頂部
一旦對應的上下文執行完畢,就從棧頂彈出,并將上下文控制權交給當前的棧。
這樣依次執行(最終都會回到全局執行上下文)
譬如,如果程序執行完畢,被彈出執行棧,然后有沒有被引用(沒有形成閉包),那么這個函數中用到的內存就會被垃圾處理器自動回收。
然后執行上下文與VO。作用域鏈,this的關系是,每一個執行上下文,都有三個重要屬性:
變量對象(Variableobject,VO)
作用域鏈(Scopechain)
this
VO與AO
VO是執行上下文的屬性(抽象概念),但是只有全局上下文的變量對象允許通過VO的屬性名稱來間接訪問(因為在全局上下文里,全局對象自身就是變量對象)。
AO(activationobject),當函數被調用者激活,AO就被創建了。
可以理解為:
在函數上下文中:VO===AO
在全局上下文中:VO===this===global
總的來說,VO中會存放一些變量信息(如聲明的變量,函數,arguments參數等等)。
作用域鏈
它是執行上下文中的一個屬性,原理和原型鏈很相似,作用很重要。
譬如流程簡述:在函數上下文中,查找一個變量foo,如果函數的VO中找到了,就直接使用,否則去它的父級作用域鏈中(parent)找。如果父級中沒找到,繼續往上找,直到全局上下文中也沒找到就報錯。
this指針
這也是JS的核心知識之一,由于內容過多,這里就不展開,僅提及部分。
注意:this是執行上下文環境的一個屬性,而不是某個變量對象的屬性。
因此:
this是沒有一個類似搜尋變量的過程
當代碼中使用了this,這個 this的值就直接從執行的上下文中獲取了,而不會從作用域鏈中搜尋
this的值只取決中進入上下文時的情況
所以經典的例子:
var baz = 200;
var bar = {
? ?baz: 100,
? ?foo: function() {
? ? ? ?console.log(this.baz);
? ?}
};
var foo = bar.foo;
// 進入環境:global
foo(); // 200,嚴格模式中會報錯,Cannot read property 'baz' of undefined
// 進入環境:global bar
bar.foo(); // 100
就要明白了上面this的介紹,上述例子很好理解。
更多參考:深入理解JavaScript系列(13):This? Yes,this!(http://www.cnblogs.com/TomXu/archive/2012/01/17/2310479.html)
回收機制
JS有垃圾處理器,所以無需手動回收內存,而是由垃圾處理器自動處理。一般來說,垃圾處理器有自己的回收策略。譬如對于那些執行完畢的函數,如果沒有外部引用(被引用的話會形成閉包),則會回收。(當然一般會把回收動作切割到不同的時間段執行,防止影響性能)。
常用的兩種垃圾回收規則是:
標記清除
引用計數
Javascript引擎基礎GC方案是(simple GC):markandsweep(標記清除),簡單解釋如下:
遍歷所有可訪問的對象。
回收已不可訪問的對象。
譬如:(出自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虛擬機作者。)
更多可以參考:
V8 內存淺析:https://zhuanlan.zhihu.com/p/33816534。
其它
可以提到跨域
譬如發出網絡請求時,會用AJAX,如果接口跨域,就會遇到跨域問題。
可以參考:ajax跨域,這應該是最全的解決方案了(https://segmentfault.com/a/1190000012469713)。
可以提到web安全
譬如瀏覽器在解析HTML時,有XSSAuditor,可以延伸到web安全相關領域
可以參考:AJAX請求真的不安全么?談談Web安全與AJAX的關系(https://segmentfault.com/a/1190000012693772)。
更多
如可以提到viewport概念,講講物理像素,邏輯像素,CSS像素等概念。如熟悉Hybrid開發的話可以提及一下Hybrid相關內容以及優化。
...
總結
上述這么多內容,目的是:梳理出自己的知識體系。
本文由于是前端向,所以知識梳理時有重點,很多其它的知識點都簡述或略去了,重點介紹的模塊總結:
瀏覽器的進程/線程模型、JS運行機制(這一塊的詳細介紹鏈接到了另一篇文章)
http規范(包括報文結構,頭部,優化,http2.0,https等)
http緩存(單獨列出來,因為它很重要)
頁面解析流程(HTML解析,構建DOM,生成CSS規則,構建渲染樹,渲染流程,復合層的合成,外鏈的處理等)
JS引擎解析過程(包括解釋階段,預處理階段,執行階段,包括執行上下文、VO、作用域鏈、this、回收機制等)
跨域相關,web安全單獨鏈接到了具體文章,其它如CSS盒模型,viewport等僅是提及概念
關于本文的價值?
本文是個人階段性梳理知識體系的成果,然后加以修繕后發布成文章,因此并不確保適用于所有人員。但是,個人認為本文還是有一定參考價值的。
寫在最后的話
還是那句話:知識要形成體系。
梳理出知識體系后,有了一個骨架,知識點不易遺忘,而且學習新知識時也會更加迅速,更重要的是容易舉一反三,可以由一個普通的問題,深挖拓展到底層原理。前端知識是無窮無盡的,本文也僅僅是簡單梳理出一個承載知識體系的骨架而已,更多的內容仍然需要不斷學習,積累。
參考資料
https://segmentfault.com/a/1190000012925872
https://www.html5rocks.com/zh/tutorials/internals/howbrowserswork/
https://coolshell.cn/articles/9666.html
http://igoro.com/archive/what-really-happens-when-you-navigate-to-a-url/
http://blog.csdn.net/dojiangv/article/details/51794535
http://bbs.csdn.net/topics/340204423
https://segmentfault.com/a/1190000004246731
http://www.bubuko.com/infodetail-1379568.html
http://fex.baidu.com/blog/2014/05/what-happen/
http://www.cnblogs.com/winter-cn/archive/2013/05/21/3091127.html
https://fanerge.github.io/%E6%B5%8F%E8%A7%88%E5%99%A8%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86-webkit%E5%86%85%E6%A0%B8%E7%A0%94%E7%A9%B6.html
http://www.cnblogs.com/TomXu/archive/2012/01/12/2308594.html
https://segmentfault.com/q/1010000000489803