<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                [TOC] # 瀏覽器輸入 URL 后發生了什么? ## 解析URL 瀏覽器通過 URL 能夠知道下面的信息: * Protocol "http" 使用HTTP協議 * Resource "/" 請求的資源是主頁(index) ## 輸入的是 URL 還是搜索的關鍵字? 當協議或主機名不合法時,瀏覽器會將地址欄中輸入的文字傳給默認的搜索引擎。大部分情況下,在把文字傳遞給搜索引擎的時候,URL會帶有特定的一串字符,用來告訴搜索引擎這次搜索來自這個特定瀏覽器。 ## 轉換非 ASCII 的 Unicode 字符 * 瀏覽器檢查輸入是否含有不是 a-z, A-Z,0-9, - 或者 . 的字符 * 這里主機名是 google.com ,所以沒有非ASCII的字符;如果有的話,瀏覽器會對主機名部分使用 Punycode 編碼 ## 檢查 HSTS 列表 瀏覽器檢查自帶的“預加載 HSTS(HTTP嚴格傳輸安全)”列表,這個列表里包含了那些請求瀏覽器只使用HTTPS進行連接的網站 如果網站在這個列表里,瀏覽器會使用 HTTPS 而不是 HTTP 協議,否則,最初的請求會使用HTTP協議發送 注意,一個網站哪怕不在 HSTS 列表里,也可以要求瀏覽器對自己使用 HSTS 政策進行訪問。瀏覽器向網站發出第一個 HTTP 請求之后,網站會返回瀏覽器一個響應,請求瀏覽器只使用 HTTPS 發送請求。然而,就是這第一個 HTTP 請求,卻可能會使用戶受到 downgrade attack 的威脅,這也是為什么現代瀏覽器都預置了 HSTS 列表。 ## DNS 查詢 * 瀏覽器檢查域名是否在緩存當中(要查看 Chrome 當中的緩存, 打開 chrome://net-internals/#dns)。 * 如果緩存中沒有,就去調用 gethostbyname 庫函數(操作系統不同函數也不同)進行查詢。 * gethostbyname 函數在試圖進行DNS解析之前首先檢查域名是否在本地 Hosts 里,Hosts 的位置 不同的操作系統有所不同 * 如果 gethostbyname 沒有這個域名的緩存記錄,也沒有在 hosts 里找到,它將會向 DNS 服務器發送一條 DNS 查詢請求。DNS 服務器是由網絡通信棧提供的,通常是本地路由器或者 ISP 的緩存 DNS 服務器。 * 查詢本地 DNS 服務器 * 如果 DNS 服務器和我們的主機在同一個子網內,系統會按照下面的 ARP 過程對 DNS 服務器進行 ARP查詢 * 如果 DNS 服務器和我們的主機在不同的子網,系統會按照下面的 ARP 過程對默認網關進行查詢 ## ARP 過程 要想發送 ARP(地址解析協議)廣播,我們需要有一個目標 IP 地址,同時還需要知道用于發送 ARP 廣播的接口的 MAC 地址。 * 首先查詢 ARP 緩存,如果緩存命中,我們返回結果:目標 IP = MAC 如果緩存沒有命中: * 查看路由表,看看目標 IP 地址是不是在本地路由表中的某個子網內。是的話,使用跟那個子網相連的接口,否則使用與默認網關相連的接口。 * 查詢選擇的網絡接口的 MAC 地址 * 我們發送一個二層( OSI 模型 中的數據鏈路層)ARP 請求: `ARP Request:` ~~~ Sender MAC: interface:mac:address:here Sender IP: interface.ip.goes.here Target MAC: FF:FF:FF:FF:FF:FF (Broadcast) Target IP: target.ip.goes.here ~~~ 根據連接主機和路由器的硬件類型不同,可以分為以下幾種情況: 直連: * 如果我們和路由器是直接連接的,路由器會返回一個 ARP Reply (見下面)。 集線器: * 如果我們連接到一個集線器,集線器會把 ARP 請求向所有其它端口廣播,如果路由器也“連接”在其中,它會返回一個 ARP Reply 。 交換機: * 如果我們連接到了一個交換機,交換機會檢查本地 CAM/MAC 表,看看哪個端口有我們要找的那個 MAC 地址,如果沒有找到,交換機會向所有其它端口廣播這個 ARP 請求。 * 如果交換機的 MAC/CAM 表中有對應的條目,交換機會向有我們想要查詢的 MAC 地址的那個端口發送 ARP 請求 * 如果路由器也“連接”在其中,它會返回一個 ARP Reply `ARP Reply:` ~~~ Sender MAC: target:mac:address:here Sender IP: target.ip.goes.here Target MAC: interface:mac:address:here Target IP: interface.ip.goes.here ~~~ 現在我們有了 DNS 服務器或者默認網關的 IP 地址,我們可以繼續 DNS 請求了: * 使用 53 端口向 DNS 服務器發送 UDP 請求包,如果響應包太大,會使用 TCP 協議 * 如果本地/ISP DNS 服務器沒有找到結果,它會發送一個遞歸查詢請求,一層一層向高層 DNS 服務器做查詢,直到查詢到起始授權機構,如果找到會把結果返回 ## 建立連接 ### 使用套接字 當瀏覽器得到了目標服務器的 IP 地址,以及 URL 中給出來端口號(http 協議默認端口號是 80, https 默認端口號是 443),它會調用系統庫函數 socket ,請求一個 TCP流套接字,對應的參數是 AF_INET/AF_INET6 和 SOCK_STREAM 。 * 這個請求首先被交給傳輸層,在傳輸層請求被封裝成 TCP segment。目標端口會被加入頭部,源端口會在系統內核的動態端口范圍內選取(Linux下是ip_local_port_range) * TCP segment 被送往網絡層,網絡層會在其中再加入一個 IP 頭部,里面包含了目標服務器的IP地址以及本機的IP地址,把它封裝成一個IP packet。 * 這個 TCP packet 接下來會進入鏈路層,鏈路層會在封包中加入 frame 頭部,里面包含了本地內置網卡的MAC地址以及網關(本地路由器)的 MAC 地址。像前面說的一樣,如果內核不知道網關的 MAC 地址,它必須進行 ARP 廣播來查詢其地址。 到了現在,TCP 封包已經準備好了,可以使用下面的方式進行傳輸: * [以太網](http://en.wikipedia.org/wiki/IEEE_802.3) * [WiFi](https://en.wikipedia.org/wiki/IEEE_802.11) * [蜂窩數據網絡](https://en.wikipedia.org/wiki/Cellular_data_communication_protocol) 對于大部分家庭網絡和小型企業網絡來說,封包會從本地計算機出發,經過本地網絡,再通過調制解調器把數字信號轉換成模擬信號,使其適于在電話線路,有線電視光纜和無線電話線路上傳輸。在傳輸線路的另一端,是另外一個調制解調器,它把模擬信號轉換回數字信號,交由下一個 網絡節點 處理。節點的目標地址和源地址將在后面討論。 大型企業和比較新的住宅通常使用光纖或直接以太網連接,這種情況下信號一直是數字的,會被直接傳到下一個 網絡節點 進行處理。 最終封包會到達管理本地子網的路由器。在那里出發,它會繼續經過自治區域(autonomous system, 縮寫 AS)的邊界路由器,其他自治區域,最終到達目標服務器。一路上經過的這些路由器會從IP數據報頭部里提取出目標地址,并將封包正確地路由到下一個目的地。IP數據報頭部 time to live (TTL) 域的值每經過一個路由器就減1,如果封包的TTL變為0,或者路由器由于網絡擁堵等原因封包隊列滿了,那么這個包會被路由器丟棄。 上面的發送和接受過程在 TCP 連接期間會發生很多次: * 客戶端選擇一個初始序列號(ISN),將設置了 SYN 位的封包發送給服務器端,表明自己要建立連接并設置了初始序列號 * 服務器端接收到 SYN 包,如果它可以建立連接: * 服務器端選擇它自己的初始序列號 * 服務器端設置 SYN 位,表明自己選擇了一個初始序列號 * 服務器端把 (客戶端ISN + 1) 復制到 ACK 域,并且設置 ACK 位,表明自己接收到了客戶端的第一個封包 * 客戶端通過發送下面一個封包來確認這次連接: * 自己的序列號+1 * 接收端 ACK+1 * 設置 ACK 位 * 數據通過下面的方式傳輸: * 當一方發送了N個 Bytes 的數據之后,將自己的 SEQ 序列號也增加N * 另一方確認接收到這個數據包(或者一系列數據包)之后,它發送一個 ACK 包,ACK 的值設置為接收到的數據包的最后一個序列號 * 關閉連接時: * 要關閉連接的一方發送一個 FIN 包 * 另一方確認這個 FIN 包,并且發送自己的 FIN 包 * 要關閉的一方使用 ACK 包來確認接收到了 FIN ### TLS 握手 * 客戶端發送一個 ClientHello 消息到服務器端,消息中同時包含了它的 Transport Layer Security (TLS) 版本,可用的加密算法和壓縮算法。 * 服務器端向客戶端返回一個 ServerHello 消息,消息中包含了服務器端的TLS版本,服務器所選擇的加密和壓縮算法,以及數字證書認證機構(Certificate Authority,縮寫 CA)簽發的服務器公開證書,證書中包含了公鑰。客戶端會使用這個公鑰加密接下來的握手過程,直到協商生成一個新的對稱密鑰 * 客戶端根據自己的信任CA列表,驗證服務器端的證書是否可信。如果認為可信,客戶端會生成一串偽隨機數,使用服務器的公鑰加密它。這串隨機數會被用于生成新的對稱密鑰 * 服務器端使用自己的私鑰解密上面提到的隨機數,然后使用這串隨機數生成自己的對稱主密鑰 * 客戶端發送一個 Finished 消息給服務器端,使用對稱密鑰加密這次通訊的一個散列值 * 服務器端生成自己的 hash 值,然后解密客戶端發送來的信息,檢查這兩個值是否對應。如果對應,就向客戶端發送一個 Finished 消息,也使用協商好的對稱密鑰加密 * 從現在開始,接下來整個 TLS 會話都使用對稱秘鑰進行加密,傳輸應用層(HTTP)內容 ## 發送 HTTP 請求 建立起安全的加密信道后,瀏覽器開始發送 HTTP 請求,一個請求報文由請求行、請求頭、空行、實體(Get 請求沒有)組成。 請求頭由通用首部、請求首部、實體首部、擴展首部組成。其中,通用首部表示無論是請求報文還是響應報文都可以使用,比如 Date;請求首部表示只有在請求報文中才有意義,分為 Accept 首部、條件請求首部、安全請求首部和代理請求首部這四類;實體首部作用于實體內容,分為內容首部和緩存首部這兩類;擴展首部表示用戶自定義的首部,通過 X- 前綴來添加。 另外需要注意的是,HTTP 請求頭是不區分大小寫的,它基于 ASCII 進行編碼,而實體可以基于其它編碼方式,由 Content-Type 決定。 ~~~ GET / HTTP/1.1 Host: google.com Connection: close [其他頭部] ~~~ “其他頭部”包含了一系列的由冒號分割開的鍵值對,它們的格式符合HTTP協議標準,它們之間由一個換行符分割開來。(這里我們假設瀏覽器沒有違反HTTP協議標準的bug,同時假設瀏覽器使用 HTTP/1.1 協議,不然的話頭部可能不包含 Host 字段,同時 GET 請求中的版本號會變成 HTTP/1.0 或者 HTTP/0.9 。) HTTP/1.1 定義了“關閉連接”的選項 "close",發送者使用這個選項指示這次連接在響應結束之后會斷開。例如: > Connection:close 不支持持久連接的 HTTP/1.1 應用必須在每條消息中都包含 "close" 選項。 在發送完這些請求和頭部之后,瀏覽器發送一個換行符,表示要發送的內容已經結束了。 ## 返回 HTTP 響應 服務器端返回一個響應碼,指示這次請求的狀態,響應的形式是這樣的: ~~~ 200 OK [響應頭部] ~~~ 然后是一個換行,接下來有效載荷(payload),也就是 www.google.com 的HTML內容。服務器下面可能會關閉連接,如果客戶端請求保持連接的話,服務器端會保持連接打開,以供之后的請求重用。 如果瀏覽器發送的HTTP頭部包含了足夠多的信息(例如包含了 Etag 頭部),以至于服務器可以判斷出,瀏覽器緩存的文件版本自從上次獲取之后沒有再更改過,服務器可能會返回這樣的響應: ~~~ 304 Not Modified [響應頭部] ~~~ 這個響應沒有有效載荷,瀏覽器會從自己的緩存中取出想要的內容。 在解析完 HTML 之后,瀏覽器和客戶端會重復上面的過程,直到HTML頁面引入的所有資源(圖片,CSS,favicon.ico等等)全部都獲取完畢,區別只是頭部的 `GET / HTTP/1.1` 會變成 `GET /$(相對www.google.com的URL) HTTP/1.1 `。 如果HTML引入了 `www.google.com` 域名之外的資源,瀏覽器會回到上面解析域名那一步,按照下面的步驟往下一步一步執行,請求中的 Host 頭部會變成另外的域名。 ## HTTP 服務器請求處理 HTTPD(HTTP Daemon)在服務器端處理請求/響應。最常見的 HTTPD 有 Linux 上常用的 Apache 和 nginx,以及 Windows 上的 IIS。 * HTTPD 接收請求 * 服務器把請求拆分為以下幾個參數: * HTTP 請求方法(GET, POST, HEAD, PUT, DELETE, CONNECT, OPTIONS, 或者 TRACE)。直接在地址欄中輸入 URL 這種情況下,使用的是 GET 方法 * 域名:google.com * 請求路徑/頁面:/ (我們沒有請求google.com下的指定的頁面,因此 / 是默認的路徑) * 服務器驗證其上已經配置了 google.com 的虛擬主機 * 服務器驗證 google.com 接受 GET 方法 * 服務器驗證該用戶可以使用 GET 方法(根據 IP 地址,身份信息等) * 如果服務器安裝了 URL 重寫模塊(例如 Apache 的 mod_rewrite 和 IIS 的 URL Rewrite),服務器會嘗試匹配重寫規則,如果匹配上的話,服務器會按照規則重寫這個請求 * 服務器根據請求信息獲取相應的響應內容,這種情況下由于訪問路徑是 "/" ,會訪問首頁文件(你可以重寫這個規則,但是這個是最常用的)。 * 服務器會使用指定的處理程序分析處理這個文件,假如 Google 使用 PHP,服務器會使用 PHP 解析 index 文件,并捕獲輸出,把 PHP 的輸出結果返回給請求者 ## 維持連接 完成一次 HTTP 請求后,服務器并不是馬上斷開與客戶端的連接。在 HTTP/1.1 中,`Connection: keep-alive` 是默認啟用的,表示持久連接,以便處理不久后到來的新請求,無需重新建立連接而增加慢啟動開銷,提高網絡的吞吐能力。在反向代理軟件 Nginx 中,持久連接超時時間默認值為 75 秒,如果 75 秒內沒有新到達的請求,則斷開與客戶端的連接。同時,瀏覽器每隔 45 秒會向服務器發送 `TCP keep-alive` 探測包,來判斷 TCP 連接狀況,如果沒有收到 ACK 應答,則主動斷開與服務器的連接。注意,`HTTP keep-alive` 和 `TCP keep-alive` 雖然都是一種保活機制,但是它們完全不相同,一個作用于應用層,一個作用于傳輸層。 ## 斷開連接 1. 服務器向客戶端發送 Alert 報文,類型為 Close Notify,通知客戶端不再發送數據,即將關閉連接,同樣,這條報文也是經過加密處理的。 2. 服務器通過調用 close 函數主動關閉連接,向客戶端發送帶有 FIN 標志位的分組,序列號為 m。 3. 客戶端確認收到該分組,向服務器發送帶有 ACK 標志位的分組,確認號為 m+1。 4. 客戶端發送完所有數據后,向服務器發送帶有 FIN 標志位的分組,序列號為 n。 5. 服務器確認收到該分組,向客戶端發送帶有 ACK 標志位的分組,序列號為 n+1。客戶端收到確認分組后,立即進入 CLOSED 狀態;同時,服務器等待 2 個 MSL(Maximum Segment Lifetime,最大報文生存時間) 的時間后,進入 CLOSED 狀態。 ## 瀏覽器解析過程 現代瀏覽器是一個及其龐大的大型軟件,在某種程度上甚至不亞于一個操作系統,它由多媒體支持、圖形顯示、GPU 渲染、進程管理、內存管理、沙箱機制、存儲系統、網絡管理等大大小小數百個組件組成。雖然開發者在開發 Web 應用時,無需關心底層實現細節,只需將頁面代碼交付于瀏覽器計算,就可以展示出豐富的內容。但頁面性能不僅僅關乎瀏覽器的實現方式,更取決于開發者的水平,對工具的熟悉程度,代碼優化是無止盡的。顯然,了解瀏覽器的基本原理,了解 W3C 技術標準,了解網絡協議,對設計、開發一個高性能 Web 應用幫助非常大。 當我們在使用 Chrome 瀏覽器時,其背后的引擎是 Google 開源的 Chromium 項目,而 Chromium 的內核則是渲染引擎 Blink(基于 Webkit)和 JavaScript 引擎 V8。在闡述瀏覽器解析 HTML 文件之前,先簡單介紹一下 Chromium 的多進程多線程架構(圖 5),它包括多個進程: * 一個 Browser 進程 * 多個 Renderer 進程 * 一個 GPU 進程 * 多個 NPAPI Render 進程 * 多個 Pepper Plugin 進程 而每個進程包括若干個線程: 一個主線程 在 Browser 進程中:渲染更新界面 在 Renderer 進程中:使用持有的內核 Blink 實例解析渲染更新界面 一個 IO 線程 在 Browser 進程中:處理 IPC 通信和網絡請求 在 Renderer 進程中:處理與 Browser 進程之間的 IPC 通信 一組專用線程 一個通用線程池 ![](https://box.kancloud.cn/82a4510d1954a1ca62537766fd647121_700x649.png) Chromium 支持多種不同的方式管理 Renderer 進程,不僅僅是每一個開啟的 Tab 頁面,iframe 頁面也包括在內,每個 Renderer 進程是一個獨立的沙箱,相互之間隔離不受影響。 * Process-per-site-instance:每個域名開啟一個進程,并且從一個頁面鏈接打開的新頁面共享一個進程(noopener 屬性除外),這是默認模式 * Process-per-site:每個域名開啟一個進程 * Process-per-tab:每個 Tab 頁面開啟一個進程 * Single process:所有頁面共享一個進程 當 Renderer 進程需要訪問網絡請求模塊(XHR、Fetch),以及訪問存儲系統(同步 Local Storage、同步 Cookie、異步 Cookie Store)時,則調用 RenderProcess 全局對象通過 IO 線程與 Browser 進程中的 RenderProcessHost 對象建立 IPC 信道,底層通過 socketpair 來實現。正由于這種機制,Chromium 可以更好地統一管理資源、調度資源,有效地減少網絡、性能開銷。 ### 主流程 頁面的解析工作是在 Renderer 進程中進行的,Renderer 進程通過在主線程中持有的 Blink 實例邊接收邊解析 HTML 內容,每次從網絡緩沖區中讀取 8KB 以內的數據。瀏覽器自上而下逐行解析 HTML 內容,經過詞法分析、語法分析,構建 DOM 樹。 當遇到外部 CSS 鏈接時,主線程調用網絡請求模塊異步獲取資源,不阻塞而繼續構建 DOM 樹。當 CSS 下載完畢后,主線程在合適的時機解析 CSS 內容,經過詞法分析、語法分析,構建 CSSOM 樹。瀏覽器結合 DOM 樹和 CSSOM 樹構建 Render 樹,并計算布局屬性,每個 Node 的幾何屬性和在坐標系中的位置,最后進行繪制展示在屏幕上。 當遇到外部 JS 鏈接時,主線程調用網絡請求模塊異步獲取資源,由于 JS 可能會修改 DOM 樹和 CSSOM 樹而造成回流和重繪,此時 DOM 樹的構建是處于阻塞狀態的。但主線程并不會掛起,瀏覽器會使用一個輕量級的掃描器去發現后續需要下載的外部資源,提前發起網絡請求,而腳本內部的資源不會識別,比如 document.write。當 JS 下載完畢后,瀏覽器調用 V8 引擎在 Script Streamer 線程中解析、編譯 JS 內容,并在主線程中執行。 :-: ![](https://box.kancloud.cn/f0808ef6881295970e12d29cbb632741_624x289.png) :-: Webkit 主流程 <br> :-: ![](https://box.kancloud.cn/83df18c74eafc1d01077c043a9e05ef4_640x162.png) :-: V8 解釋流程,Chrome 66 以前對比 Chrome 66 ### HTML解析 HTML解析是瀏覽器的HTML解析器把HTML解析成dom tree,而在解析過程,瀏覽器根據HTML文件的結構從上到下解析html,HTML元素是以深度優先的方式解析,而script、link、style等標簽會使解析過程產生阻塞,阻塞的情況有: * 外部樣式會阻塞內部腳本的執行 * 外部樣式與外部腳本并行加載,但外部樣式會阻塞外部腳本執行 * 如果外部腳本帶有async屬性,則外部腳本的加載與執行不受外部樣式影響 * 如果link標簽是動態創建(js生成),不管有無async屬性,都不會阻塞外部腳本的加載與執行 ### CSS解析 CSS Parser作用就是將很多個CSS文件中的樣式合并解析出具有樹形結構Style Rules,在對樣式解析的過程中,默認CSS選擇器是從右往左進行解析的。 ### 腳本執行 瀏覽器解析HTML時,當遇到`<script>`標簽就會立即解析腳本,同時阻塞解析文檔直到腳本執行完畢(你可能問為什么要這樣設計,明顯啊,腳本的執行是改變css和dom,會造成render tree不停的重繪和重排的),而當`<script>`是引入外部js文件時,會阻塞到js文件下載完成并且執行完成為止(除非加了defer或者async屬性)。腳本在解析過程中將對dom或css的操作解析出來加入到DOM Tree和cssom中。 ## 渲染流程 當 DOM 樹構建完畢后,還需經過好幾次轉換,它們有多種中間表示。首先計算布局、繪圖樣式,轉換為 RenderObject 樹(也叫 Render 樹)。再轉換為 RenderLayer 樹,當 RenderObject 擁有同一個坐標系(比如 canvas、absolute)時,它們會合并為一個 RenderLayer,這一步由 CPU 負責合成。接著轉換為 GraphicsLayer 樹,當 RenderLayer 滿足合成層條件(比如 transform,熟知的硬件加速)時,會有自己的 GraphicsLayer,否則與父節點合并,這一步同樣由 CPU 負責合成。最后,每個 GraphicsLayer 都有一個 GraphicsContext 對象,負責將層繪制成位圖作為紋理上傳給 GPU,由 GPU 負責合成多個紋理,最終顯示在屏幕上。 :-: ![](https://box.kancloud.cn/0f6e15daad61666a71d1488c967b2993_650x268.png) :-: 從 DOM 樹到 GraphicsLayer 樹的轉換 另外,為了提升渲染性能效率,瀏覽器會有專用的 Compositor 線程來負責層合成,同時負責處理部分交互事件(比如滾動、觸摸),直接響應 UI 更新而不阻塞主線程。主線程把 RenderLayer 樹同步給 Compositor 線程,由它開啟多個 Rasterizer 線程,進行光柵化處理,在可視區域以瓦片為單位把頂點數據轉換為片元,最后交付給 GPU 進行最終合成渲染。 :-: ![](https://box.kancloud.cn/3ba88393023a38b0e90e903c40bdf822_720x331.png) :-: Chromium 多線程渲染 ## 頁面生命周期 頁面從發起請求開始,結束于跳轉、刷新或關閉,會經過多次狀態變化和事件通知,因此了解整個過程的生命周期非常有必要。瀏覽器提供了 [Navigation Timing](https://link.zhihu.com/?target=https%3A//www.w3.org/TR/navigation-timing-2/) 和 [Resource Timing](https://link.zhihu.com/?target=https%3A//www.w3.org/TR/resource-timing-2/) 兩種 API 來記錄每一個資源的事件發生時間點,你可以用它來收集 RUM(Real User Monitoring,真實用戶監控)數據,發送給后端監控服務,綜合分析頁面性能來不斷改善用戶體驗。下圖表示 HTML 資源加載的事件記錄全過程,而中間黃色部分表示其它資源(CSS、JS、IMG、XHR)加載事件記錄過程,它們都可以通過調用 `window.performance.getEntries()` 來獲取具體指標數據。 :-: ![](https://box.kancloud.cn/ba4c5ac012a0e60ac57858da133d82ee_720x226.png) :-: 頁面加載事件記錄流程 衡量一個頁面性能的方式有很多,但能給用戶帶來直接感受的是頁面何時渲染完成、何時可交互、何時加載完成。其中,有兩個非常重要的生命周期事件,`DOMContentLoaded` 事件表示 DOM 樹構建完畢,可以安全地訪問 DOM 樹所有 Node 節點、綁定事件等等;load 事件表示所有資源都加載完畢,圖片、背景、內容都已經完成渲染,頁面處于可交互狀態。但是迄今為止瀏覽器并不能像 Android 和 iOS app 一樣完全掌控應用的狀態,在前后臺切換的時候,重新分配資源,合理地利用內存。實際上,現代瀏覽器都已經在做這方面的相關優化,并且自 Chrome 68 以后提供了[`Page Lifecycle`](https://link.zhihu.com/?target=https%3A//wicg.github.io/page-lifecycle/spec.html) API,定義了全新的瀏覽器生命周期,讓開發者可以構建更出色的應用。 :-: ![](https://box.kancloud.cn/284d43e333924fc081f16328692d4ecb_720x487.png) :-: 新版頁面生命周期 現在,你可以通過給 window 和 document 綁定上所有生命周期監聽事件,來監測頁面切換、用戶交互行為所觸發的狀態變化過程。不過,開發者只能感知事件在何時發生,不能直接獲取某一刻的頁面狀態(圖中的 STATE)。即使如此,利用這個 API,也可以讓腳本在合適的時機執行某項任務或進行界面 UI 反饋。 ![](https://box.kancloud.cn/dfb02dd3018e45a337cce2e6076f33d0_720x349.png) # 參考資料 [當···時發生了什么?](https://github.com/skyline75489/what-happens-when-zh_CN#gpu) [瀏覽器輸入 URL 后發生了什么?](https://zhuanlan.zhihu.com/p/43369093) [一篇文章搞定前端面試](https://juejin.im/post/5bbaa549e51d450e827b6b13#heading-5)
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看