從輸入URL到頁面加載的過程?由一道題完善本身的前端知識體系!

梳理主幹流程

這道題上,如何回答呢?先梳理一個骨架。javascript

知識體系中,最重要的是骨架,脈絡。有了骨架後,才方便填充細節。因此,先梳理下主幹流程:css

  1. 從瀏覽器接收url到開啓網絡請求線程(這一部分能夠展開瀏覽器的機制以及進程與線程之間的關係)
  2. 開啓網絡線程到發出一個完整的http請求(這一部分涉及到dns查詢,tcp/ip請求,五層因特網協議棧等知識)
  3. 從服務器接收到請求到對應後臺接收到請求(這一部分可能涉及到負載均衡,安全攔截以及後臺內部的處理等等)
  4. 後臺和前臺的http交互(這一部分包括http頭部、響應碼、報文結構、cookie等知識,能夠提下靜態資源的cookie優化,以及編碼解碼,如gzip壓縮等)
  5. 單獨拎出來的緩存問題,http的緩存(這部分包括http緩存頭部,etag,catch-control等)
  6. 瀏覽器接收到http數據包後的解析流程(解析html-詞法分析而後解析成dom樹、解析css生成css規則樹、合併成render樹,而後layout、painting渲染、複合圖層的合成、GPU繪製、外鏈資源的處理、loaded和domcontentloaded等)
  7. CSS的可視化格式模型(元素的渲染規則,如包含塊,控制框,BFC,IFC等概念)
  8. JS引擎解析過程(JS的解釋階段,預處理階段,執行階段生成執行上下文,VO,做用域鏈、回收機制等等)
  9. 其它(能夠拓展不一樣的知識模塊,如跨域,web安全,hybrid模式等等內容)

梳理出主幹骨架,而後就須要往骨架上填充細節內容html

梳理出主幹骨架,而後就須要往骨架上填充細節內容

這一部分展開的內容是:瀏覽器進程/線程模型,JS的運行機制。前端

多進程的瀏覽器

瀏覽器是多進程的,有一個主控進程,以及每個tab頁面都會新開一個進程(某些狀況下多個tab會合並進程)。html5

進程可能包括主控進程,插件進程,GPU,tab頁(瀏覽器內核)等等。java

  • Browser進程:瀏覽器的主進程(負責協調、主控),只有一個
  • 第三方插件進程:每種類型的插件對應一個進程,僅當使用該插件時才建立
  • GPU進程:最多一個,用於3D繪製
  • 瀏覽器渲染進程(內核):默認每一個Tab頁面一個進程,互不影響,控制頁面渲染,腳本執行,事件處理等(有時候會優化,如多個空白tab會合併成一個進程)

以下圖:node

多線程的瀏覽器內核

每個tab頁面能夠看做是瀏覽器內核進程,而後這個進程是多線程的,它有幾大類子線程:nginx

  • GUI線程
  • JS引擎線程
  • 事件觸發線程
  • 定時器線程
  • 網絡請求線程

能夠看到,裏面的JS引擎是內核進程中的一個線程,這也是爲何常說JS引擎是單線程的。es6

解析URL

輸入URL後,會進行解析(URL的本質就是統一資源定位符web

  • protocol,協議頭,譬若有http,ftp等
  • host,主機域名或IP地址
  • port,端口號
  • path,目錄路徑
  • query,即查詢參數
  • fragment,即 #後的hash值,通常用來定位到某個位置

網絡請求都是單獨的線程

每次網絡請求時都須要開闢單獨的線程進行,譬如若是URL解析到http協議,就會新建一個網絡線程去處理資源下載。

所以瀏覽器會根據解析出得協議,開闢一個網絡線程,前往請求資源(這裏,暫時理解爲是瀏覽器內核開闢的,若有錯誤,後續修復)。

更多

因爲篇幅關係,這裏就大概介紹一個主幹流程,關於瀏覽器的進程機制,更多能夠參考之前總結的一篇文章(由於內容實在過多,裏面包括JS運行機制,進程線程的詳解)。

從瀏覽器多進程到JS單線程,JS運行機制最全面的一次梳理:segmentfault.com/a/119000001…

開啓網絡線程到發出一個完整的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-SinceIf-None-Match分別和響應頭部的 Last-ModifiedETag對應。

還有不少的分析方法,這裏不一一贅述。

請求/響應實體

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通常是由apachetomcat等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並利用對稱加密算法進行加密。

這裏放一張圖(來源:阮一峯-圖解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.1http1.0,這些不一樣的頭部是屬於不一樣http時期的。

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

<META HTTP-EQUIV="Pragma" CONTENT="no-cache">
複製代碼

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

頭部的區別

首先明確,http的發展是從http1.0到http1.1,而在http1.1中,出了一些新內容,彌補了http1.0的不足。

http1.0中的緩存控制:

  • Pragma:嚴格來講,它不屬於專門的緩存控制頭部,可是它設置 no-cache時可讓本地強緩存失效(屬於編譯控制,來實現特定的指令,主要是由於兼容http1.0,因此之前又被大量應用)
  • Expires:服務端配置的,屬於強緩存,用來控制在規定的時間以前,瀏覽器不會發出請求,而是直接使用本地緩存,注意,Expires通常對應服務器端時間,如Expires:Fri,30Oct 1998 14:19:41
  • If-Modified-Since/Last-Modified:這兩個是成對出現的,屬於協商緩存的內容,其中瀏覽器的頭部是 If-Modified-Since,而服務端的是Last-Modified,它的做用是,在發起請求時,若是If-Modified-SinceLast-Modified匹配,那麼表明服務器資源並未改變,所以服務端不會返回資源實體,而是隻返回頭部,通知瀏覽器可使用本地緩存。Last-Modified,顧名思義,指的是文件最後的修改時間,並且只能精確到 1s之內

http1.1中的緩存控制:

  • Cache-Control:緩存控制頭部,是瀏覽器的頭部,有no-cachemax-age等多種取值
  • Max-Age:服務端配置的,用來控制強緩存,在規定的時間以內,瀏覽器無需發出請求,直接使用本地緩存,注意,Max-AgeCache-Control頭部的值,不是獨立的頭部,譬如 Cache-Control:max-age=3600,並且它值得是絕對時間,由瀏覽器本身計算
  • If-None-Match/E-tag:這兩個是成對出現的,屬於協商緩存的內容,其中瀏覽器的頭部是 If-None-Match,而服務端的是E-tag,一樣,發出請求後,若是If-None-MatchE-tag匹配,則表明內容未變,通知瀏覽器使用本地緩存,和Last-Modified不一樣,E-tag更精確,它是相似於指紋同樣的東西,基於FileEtagINodeMtimeSize生成,也就是說,只要文件變,指紋就會變,並且沒有1s精確度的限制。

Max-Age相比Expires?

Expires使用的是服務器端的時間,可是有時候會有這樣一種狀況-客戶端時間和服務端不一樣步。那這樣,可能就會出問題了,形成了瀏覽器本地的緩存無用或者一直沒法過時,因此通常http1.1後不推薦使用Expires。而 Max-Age使用的是客戶端本地時間的計算,所以不會有這個問題,所以推薦使用 Max-Age

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

E-tag相比Last-Modified?

Last-Modified

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

E-tag

  • 是一種指紋機制,表明文件相關指紋
  • 只有文件變纔會變,也只要文件變就會變,
  • 也沒有精確時間的限制,只要文件一遍,立馬E-tag就不同了

若是同時帶有E-tagLast-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頁面:(如下部分的內容出自參考來源,修改了下格式)

<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>
複製代碼

瀏覽器的處理以下:

列舉其中的一些重點過程:

  • 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,即重繪。意味着元素髮生的改變只是影響了元素的一些外觀之類的時候(例如,背景色,邊框顏色,文字顏色等),此時只須要應用新樣式繪製這個元素就能夠了

迴流的成本開銷要高於重繪,並且一個節點的迴流每每回致使子節點以及同級節點的迴流,因此優化方案中通常都包括,儘可能避免迴流。

什麼會引發迴流?

  • 頁面渲染初始化
  • DOM結構改變,好比刪除了某個節點
  • render樹變化,好比減小了padding
  • 窗口resize
  • 最複雜的一種:獲取某些屬性,引起迴流。

不少瀏覽器會對迴流作優化,會等到數量足夠時作一次批處理迴流,可是除了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
  • 瀏覽器的優化,通常現代瀏覽器有優化,在腳本阻塞時,也會繼續下載其它資源(固然有併發上限),可是雖然腳本能夠並行下載,解析過程仍然是阻塞的,也就是說必須這個腳本執行完畢後纔會接下來的解析,並行下載只是一種優化而已
  • deferasync,普通的腳本是會阻塞瀏覽器解析的,可是能夠加上deferasync屬性,這樣腳本就變成異步了,能夠等到解析完畢後再執行

注意,deferasync是有區別的: 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),裏面要麼只包含塊框,要麼只包含行內框(不能混雜),若是塊框內部有塊級元素也有行內元素,那麼行內元素會被匿名塊框包圍

關於匿名塊框的生成,示例:

<DIV>
    Some text
    <P>
    More text
    </P>
</DIV>
複製代碼

div生成了一個塊框,包含了另外一個塊框p以及文本內容Sometext,此時 Sometext文本會被強制加到一個匿名的塊框裏面,被div生成的塊框包含(其實這個就是 IFC中提到的行框,包含這些行內框的這一行匿名塊造成的框,行框和行內框不一樣)。

換句話說:若是一個塊框在其中包含另一個塊框,那麼咱們強迫它只能包含塊框,所以其它文本內容生成出來的都是匿名塊框(而不是匿名行內框)

行內框

  • 一個行內元素生成一個行內框
  • 行內元素能排在一行,容許左右有其它元素

關於匿名行內框的生成,示例:

<P>Some <EM>emphasized</EM> text</P>
複製代碼

P元素生成一個塊框,其中有幾個行內框(如EM),以及文本Sometext,此時會專門爲這些文本生成匿名行內框。

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
  • positionabsolutefixed
  • displayinline-block, flex, inline-flextabletable-celltable-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的分層顯示機制等

這裏不一一展開,更多請參考: bbs.csdn.net/topics/3402…

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核心(晉級高手必讀篇)www.cnblogs.com/TomXu/archi…

解釋器解釋完語法規則後,就開始執行,而後整個執行流程中大體包含如下概念:

  • 執行上下文,執行堆棧概念(如全局上下文,當前活動上下文)
  • 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!(www.cnblogs.com/TomXu/archi…)

回收機制

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 內存淺析:zhuanlan.zhihu.com/p/33816534。

其它

能夠提到跨域

譬如發出網絡請求時,會用AJAX,若是接口跨域,就會遇到跨域問題。

能夠參考:ajax跨域,這應該是最全的解決方案了(segmentfault.com/a/119000001…)。

能夠提到web安全

譬如瀏覽器在解析HTML時,有XSSAuditor,能夠延伸到web安全相關領域

能夠參考:AJAX請求真的不安全麼?談談Web安全與AJAX的關係(segmentfault.com/a/119000001…)。

更多

如能夠提到viewport概念,講講物理像素,邏輯像素,CSS像素等概念。如熟悉Hybrid開發的話能夠說起一下Hybrid相關內容以及優化。

總結

上述這麼多內容,目的是:梳理出本身的知識體系。

本文因爲是前端向,因此知識梳理時有重點,不少其它的知識點都簡述或略去了,重點介紹的模塊總結:

  • 瀏覽器的進程/線程模型、JS運行機制(這一塊的詳細介紹連接到了另外一篇文章)
  • http規範(包括報文結構,頭部,優化,http2.0,https等)
  • http緩存(單獨列出來,由於它很重要)
  • 頁面解析流程(HTML解析,構建DOM,生成CSS規則,構建渲染樹,渲染流程,複合層的合成,外鏈的處理等)
  • JS引擎解析過程(包括解釋階段,預處理階段,執行階段,包括執行上下文、VO、做用域鏈、this、回收機制等)
  • 跨域相關,web安全單獨連接到了具體文章,其它如CSS盒模型,viewport等僅是說起概念

關於本文的價值?

本文是我的階段性梳理知識體系的成果,而後加以修繕後發佈成文章,所以並不確保適用於全部人員。可是,我的認爲本文仍是有必定參考價值的。

寫在最後的話

仍是那句話:知識要造成體系

梳理出知識體系後,有了一個骨架,知識點不易遺忘,並且學習新知識時也會更加迅速,更重要的是容易觸類旁通,能夠由一個普通的問題,深挖拓展到底層原理。前端知識是無窮無盡的,本文也僅僅是簡單梳理出一個承載知識體系的骨架而已,更多的內容仍然須要不斷學習,積累。

參考資料

相關文章
相關標籤/搜索