整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          如何使用JavaScript解析URL

          Web 開發中,有許多情況需要解析 URL,這篇主要學習如何使用 URL 對象實現這一點。

          開始

          創建一個以下內容的 HTML 文件,并在瀏覽器中打開。

          <html> 
           <head> 
           <title>JavaScript URL parsing</title> 
           </head> 
           <body> 
           <script> 
           // 激動人心的代碼即將寫在這里 
           </script> 
           </body> 
          </html> 
          

          如果你想嘗試本文中的任何內容,可以將其放在 <script> 標記中,保存,重新加載頁面,看看會發生什么! 在本教程中,將使用 console.log 來打印所需要的內容,你可以打開開發都工具,來查看內容。

          什么是 URL

          這應該是相當簡單的,但讓我們說清楚。 URL 是網頁的地址,可以在瀏覽器中輸入以獲取該網頁的唯一內容。 可以在地址欄中看到它:



          URL 是統一資源定位符,對可以從互聯網上得到的資源的位置和訪問方法的一種簡潔的表示,是互聯網上標準資源的地址。互聯網上的每個文件都有一個唯一的 URL,它包含的信息指出文件的位置以及瀏覽器應該怎么處理它。

          此外,如果你不熟悉基本 URL 路徑的工作方式,可以查看此文學習。

          URL 不都長的一樣的

          這是一個快速提醒 - 有時 URL 可能非常奇怪,如下:

          :1234/page/?a=b

          file:///Users/username/folder/file.png

          獲取當前URL

          獲取當前頁面的 URL 非常簡單 - 我們可以使用 window.location。

          試著把這個添加到我們形如寫的的腳本中:

          console.log(window.location); 
          

          查看瀏覽器的控制臺:



          不是你想要的?這是因為它不返回你在瀏覽器中看到的實際 URL 地址——它返回的是一個 URL 對象。使用這個 URL 對象,我們可以解析 URL 的不同部分,接下來就會講到。

          創建 URL 對象

          很快就會看到,可以使用 URL 對象來了解 URL 的不同部分。如果你想對任何 URL 執行此操作,而不僅僅是當前頁面的 URL,該怎么辦? 我們可以通過創建一個新的 URL 對象來實現。 以下是如何創建一個:

          var myURL = new URL('https://example.com'); 
          

          就這么簡單! 可以打印 myURL 來查看 myURL 的內容:

          console.log(myURL); 
          



          出于本文的目的,將 myURL 設置為這個值:

          var myURL = new URL('https://example.com:4000/folder/page.html?x=y&a=b#section-2') 
          

          將其復制并粘貼到 <script> 元素中,以便你可以繼續操作! 這個 URL 的某些部分可能不熟悉,因為它們并不總是被使用 - 但你將在下面了解它們,所以不要擔心!

          URL 對象的結構

          使用 URL 對象,可以非常輕松地獲取 URL 的不同部分。 以下是你可以從 URL 對象獲得的所有內容。 對于這些示例,我們將使用上面設置的 myURL。

          href

          URL 的 href 基本上是作為字符串(文本)的整個 URL。如果你想把頁面的 URL 作為字符串而不是 URL 對象,你可以寫 window.location.href。

          console.log(myURL.href); 
          // Output: "https://example.com:4000/folder/page.html?x=y&a=b#section-2" 
          

          協議 (protocol)

          URL的協議是一開始的部分。這告訴瀏覽器如何訪問該頁面,例如通過 HTTP 或 HTTPS。 但是還有很多其他協議,比如 ftp(文件傳輸協議)和 ws(WebSocket)。通常,網站將使用 HTTP 或 HTTPS。

          雖然如果你的計算機上打開了文件,你可能正在使用文件協議! URL對象的協議部分包括:,但不包括 //。 讓我們看看 myURL 吧!

          console.log(myURL.protocol); 
          // Output: "https:" 
          

          主機名(hostname)

          主機名是站點的域名。 如果你不熟悉域名,則它是在瀏覽器中看到的URL的主要部分 - 例如 google.com 或codetheweb.blog。

          console.log(myURL.hostname); 
          // Output: "example.com" 
          

          端口(port)

          URL 的端口號位于域名后面,用冒號分隔(例如 example.com:1234)。 大多數網址都沒有端口號,這種情況非常罕見。

          端口號是服務器上用于獲取數據的特定“通道” - 因此,如果我擁有 example.com,我可以在多個不同的端口上發送不同的數據。 但通常域名默認為一個特定端口,因此不需要端口號。 來看看 myURL 的端口號:

          console.log(myURL.port); 
          // Output: "4000" 
          

          主機(host)

          主機只是主機名和端口放在一起,嘗試獲取 myURL 的主機:

          console.log(myURL.host); 
          // Output: "example.com:4000" 
          

          來源(origin)

          origin 由 URL 的協議,主機名和端口組成。 它基本上是整個 URL,直到端口號結束,如果沒有端口號,到主機名結束。

          console.log(myURL.origin); 
          // Output: "https://example.com:4000" 
          

          pathname(文件名)

          pathname 從域名的最后一個 “/” 開始到 “?” 為止,是文件名部分,如果沒有 “?” ,則是從域名最后的一個 “/” 開始到 “#” 為止 , 是文件部分, 如果沒有 “?” 和 “#” , 那么從域名后的最后一個 “/” 開始到結束 , 都是文件名部分。

          console.log(myURL.pathname); 
          // Output: "/folder/page.html" 
          

          錨點(hash)

          從 “#” 開始到最后,都是錨部分。可以將哈希值添加到 URL 以直接滾動到具有 ID 為該值的哈希值 的元素。 例如,如果你有一個 id 為 hello 的元素,則可以在 URL 中添加 #hello 就可以直接滾動到這個元素的位置上。通過以下方式可以在 URL 獲取 “#” 后面的值:

          console.log(myURL.hash); 
          // Output: "#section-2" 
          

          查詢參數 (search)

          你還可以向 URL 添加查詢參數。它們是鍵值對,意味著將特定的“變量”設置為特定值。 查詢參數的形式為 key=value。 以下是一些 URL 查詢參數的示例:

          ?key1=value1&key2=value2&key3=value3 
          

          請注意,如果 URL 也有 錨點(hash),則查詢參數位于 錨點(hash)(也就是 ‘#’)之前,如我們的示例 URL 中所示:

          console.log(myURL.search); 
          // Output: "?x=y&a=b" 
          

          但是,如果我們想要拆分它們并獲取它們的值,那就有點復雜了。

          使用 URLSearchParams 解析查詢參數

          要解析查詢參數,我們需要創建一個 URLSearchParams 對象,如下所示:

          var searchParams = new URLSearchParams(myURL.search); 
          

          然后可以通過調用 searchParams.get('key')來獲取特定鍵的值。 使用我們的示例網址 - 這是原始搜索參數:

          ?x=y&a=b 
          

          因此,如果我們調用 searchParams.get('x'),那么它應該返回 y,而 searchParams.get('a')應該返回 b,我們來試試吧!

          console.log(searchParams.get('x')); 
          // Output: "y" 
          console.log(searchParams.get('a')); 
          // Output: "b" 
          

          擴展

          獲取 URL 的中參數

          方法一:正則法

          function getQueryString(name) { 
           var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i'); 
           var r = window.location.search.substr(1).match(reg); 
           if (r != null) { 
           return unescape(r[2]); 
           } 
           return null; 
          } 
          // 這樣調用: 
          alert(GetQueryString("參數名1")); 
          alert(GetQueryString("參數名2")); 
          alert(GetQueryString("參數名3")); 
          

          方法二:split拆分法

          function GetRequest() { 
           var url = location.search; //獲取url中"?"符后的字串 
           var theRequest = new Object(); 
           if (url.indexOf("?") != -1) { 
           var str = url.substr(1); 
           strstrs = str.split("&"); 
           for(var i = 0; i < strs.length; i ++) { 
           theRequest[strs[i].split("=")[0]] = unescape(strs[i].split("=")[1]); 
           } 
           } 
           return theRequest; 
          } 
          var Request = new Object(); 
          Request = GetRequest(); 
          // var 參數1,參數2,參數3,參數N; 
          // 參數1 = Request['參數1']; 
          // 參數2 = Request['參數2']; 
          // 參數3 = Request['參數3']; 
          // 參數N = Request['參數N']; 
          

          修改 URL 的中某個參數值

          薦閱讀:

          阿里架構師精選Nginx+Redis+Sping+SpringBoot源碼級PDF文檔分享

          微服務+Docker完美教程,被阿里架構師匯集到這2份文檔里面了!

          引言

          對于面試常問的從瀏覽器輸入 URL 到頁面展示過程發生了什么?,我想大家都或多或少能說出一二。但是,其實這個問題很有深度,而你是否回答的有深度,在很大程度上會影響到面試官對你的印象。

          并且,網上各種資料都是淺嘗輒止地講解這個過程,經常會出現今天看到這個版本,明天看到另一個版本地情況。所以,這次我們就來深入淺出一下這整個過程~

          一、Chrome 多進程架構

          首先,在開始講解整個過程前,我們需要認識一下 Chrome 多進程架構。因為,從瀏覽器輸入 URL 到頁面渲染的整個過程都是由 Chrome 架構中的各個進程之間的配合完成。

          Chrome 的多進程架構:

          • 瀏覽器進程,它負責用戶界面(地址欄、菜單等等)、子進程的管理(例如,進程間通信和數據傳遞)、存儲等等
          • 渲染進程,它負責將接收到的 HTML 文檔和 JavaScript 等轉化為用戶界面
          • 網絡進程,它負責網絡資源的請求,例如 HTTP請求、WebSocket 模塊
          • GPU(圖形處理器)進程,它負責對 UI 界面的展示
          • 插件進程,它負責對插件的管理

          二、過程詳解

          2.1 解析輸入

          發生這個過程的前提,用戶在地址欄中輸入了 URL,而地址欄會根據用戶輸入,做出如下判斷:

          • 輸入的是非 URL 結構的字符串,則會用瀏覽器默認的搜索引擎搜索該字符串
          • 輸入的是 URL 結構字符串,則會構建完整的 URL 結構,瀏覽器進程會將完整的 URL 通過進程間通信,即 IPC,發送給網絡進程

          2.2 請求過程

          在網絡進程接收到 URL 后,并不是馬上對指定 URL 進行請求。首先,我們需要進行 DNS 解析域名得到對應的 IP,然后通過 ARP 解析 IP 得到對應的 MAC(Media Access Control Address)地址。

          域名是我們取代記憶復雜的 IP 的一種解決方案,而 IP 地址才是目標在網絡中所被分配的節點。MAC 地址是對應目標網卡所在的固定地址。

          1. DNS 解析

          而 DNS 解析域名的過程分為以下幾個步驟:

          • 詢問瀏覽器 DNS 緩存
          • 詢問本地操作系統 DNS 緩存(即查找本地 host 文件)
          • 詢問 ISP(Internet Service Provider)互聯網服務提供商(例如電信、移動)的 DNS 服務器
          • 詢問根服務器,這個過程可以進行遞歸和迭代兩種查找的方式,兩者都是先詢問頂級域名服務器查找

          2. 通信過程

          首先,建立 TCP 連接,即三次握手過程

          • 客戶端發送標有 SYN 的數據包,表示我將要發送請求。
          • 服務端發送標有 SYN/ACK 的數據包,表示我已經收到通知,告知客戶端發送請求。
          • 客戶端發送標有 ACK 的數據包,表示我要開始發送請求,準備被接受。



          然后,利用 TCP 通道進行數據傳輸

          • 服務端接收到數據包,并發送確認數據包已收到的消息到客戶端,不斷重復這個過程
          • 客戶端在發送一個數據包后,未接收到服務端的確定消息,則重新發送該數據包,即 TCP 的重發機制
          • 當接收完所有的數據包后,接收端會按照 TCP 頭中的需要進行排序,形成完整的數據

          最后,斷開 TCP 連接,即四次握手過程

          • 客戶端發送請求,申請斷開連接,進入等待階段,此時不會發送數據,但是會繼續接收數據。
          • 服務端接收請求后,告知客戶端已明白,此時服務端進入等待狀態,不會再接收數據,但是會繼續發送數據。
          • 客戶端收到后,進入下一階段等待。
          • 服務端發送完剩余的數據后,告知客戶端可以斷開連接,此時服務端不會發送和接收數據。
          • 客戶端收到后,告知服務端我開始斷開連接。
          • 服務端收到后,開始斷開連接。



          而這整個過程的客戶端則是網絡進程。并且,在數據傳輸的過程還可能會發生的重定向的情況,即當網絡進程接收到狀態碼為 3xx 的響應報文,則會根據響應報文首部字段中的 Location 字段的值進行重新向,即會重新發起請求

          3. 數據處理

          當網絡進程接收到的響應報文狀態碼,進行相應的操作。例如狀態碼為 200 OK 時,會解析響應報文中的 Content-Type 首部字段,例如我們這個過程 Content-Type 會出現 application/javascript、text/css、text/html,即對應 Javascript 文件、CSS 文件、HTML 文件。

          詳細的 MIME 類型講解可以看 MDN

          2.3 創建渲染進程

          當前需要渲染 HTML 時,則需要創建渲染進程,用于后期渲染 HTML。而對于渲染進程,如果是同一站點是可以共享一個渲染進程,例如 a.abc.com 和 c.abc.com 可以共享一個渲染渲染進程。否則,需要重新創建渲染進程

          需要注意的是,同站指的是頂級域名二級域名相等

          2.4 開始渲染

          在創建完渲染進程后,網絡進程會將接收到的 HTML、JavaScript 等數據傳遞給渲染進程。而在渲染進程接收完數據后,此時用戶界面上會發生這幾件事:

          • 更新地址欄的安全狀態
          • 更新地址欄的 URL
          • 前進后退此時 enable,顯示正在加載狀態
          • 更新網頁



          2.5 渲染過程

          大家都知道頁面渲染的過程也是面試中單獨會考的點,并且時常會由這個點延申出另一個問題,即如何避免回流和重繪。

          渲染過程,是整個從理器輸入 URL 到頁面渲染過程的最后一步。而頁面渲染的過程可以分為 9 個步驟:

          • 解析 HTML 生成 DOM 樹
          • 解析 CSS 生成 CSSOM
          • 加載或執行 JavaScript
          • 生成渲染樹(Render Tree)
          • 布局
          • 分層
          • 生成繪制列表
          • 光柵化
          • 顯示

          2.5.1 構建 DOM 樹

          由于網絡進程傳輸給渲染進程的是 HTML 字符串,所以,渲染進程需要將 HTML 字符串轉化成 DOM 樹。例如:



          需要注意的是這個 DOM 樹不同于 Chrome-devtool 中 Element 選項卡的 DOM 樹,它是存在內存中的,用于提供 JavaScript 對 DOM 的操作。

          2.5.2 構建 CSSOM

          構建 CSSOM 的過程,即通過解析 CSS 文件、style 標簽、行內 style 等,生成 CSSOM。而這個過程會做這幾件事:

          • 規范 CSS,即將 color: blue 轉化成 color: rgb() 形式,可以理解成類似 ES6 轉 ES5 的過程
          • 計算元素樣式,例如 CSS 樣式會繼承父級的樣式,如 font-size、color 之類的。



          CSS Object Model 是一組允許用 JavaScript 操縱 CSS 的 API。詳細 API 講解可以看 MDN

          2.5.3 加載 JavaScript

          通常情況下,在構建 DOM 樹或 CSSOM 的同時,如果也要加載 JavaScript,則會造成前者的構建的暫停。當然,我們可以通過 defer 或 sync 來實現異步加載 JavaScript。雖然 defer 和 sync 都可以實現異步加載 JavaScript,但是前者是在加載后,等待 CSSOM 和 DOM 樹構建完后才執行 JavaScript,而后者是在異步加載完馬上執行,即使用 sync 的方式仍然會造成阻塞。

          而 JavaScript 執行的過程,即編譯和運行 JavaScript 的過程。由于 JavaScript 是解釋型的語言。所以這個過程會是這樣的:

          • 針對每句代碼進行分行處理,即 Token 化
          • 根據 Token,生成 AST(Abstract Sytanx Tree) 抽象語法樹和創建上下文
          • 解釋器解析和執行 AST,生成字節碼。
          • 編譯器針對需要反復執行的代碼,生成對應的機器碼,提高運行效率

          2.5.4 生成渲染樹(Render Tree)

          在有了 DOM 樹和 CSSOM 之后,需要將兩者結合生成渲染樹 Render Tree,并且這個過程會去除掉那些 display: node 的節點。此時,渲染樹就具備元素和元素的樣式信息。

          2.5.5 布局

          根據 Render Tree 渲染樹,對樹中每個節點進行計算,確定每個節點在頁面中的寬度、高度和位置。

          需要注意的是,第一次確定節點的大小和位置的過程稱為布局,而第二次才被稱為回流

          2.5.6 分層

          由于層疊上下文的存在,渲染引擎會為具備層疊上下文的元素創建對應的圖層,而諸多圖層的疊加就形成了我們看到的一些頁面效果。例如,一些 3D 的效果、動畫就是基于圖層而形成的。

          值得一提的是,對于內容溢出存在滾輪的情況也會進行分層

          2.5.7 生成繪制列表

          對于存在圖層的頁面部分,需要進行有序的繪制,而對于這個過程,渲染引擎會將一個個圖層的繪制拆分成繪制指令,并按照圖層繪制順序形成一個繪制列表。

          2.5.8 光柵化

          有了繪制列表后,渲染引擎中的合成線程會根據當前視口的大小將圖層進行分塊處理,然后合成線程會對視口附近的圖塊生成位圖,即光柵化。而渲染進程也維護了一個柵格化的線程池,專門用于將圖塊轉為位圖。

          柵格化的過程通常會使用 GPU 加速,例如使用 wil-change、opacity,就會通過 GPU 加速顯示

          2.5.9 顯示

          當所有的圖塊都經過柵格化處理后,渲染引擎中的合成線程會生成繪制圖塊的指令,提交給瀏覽器進程。然后瀏覽器進程將頁面繪制到內存中。最后將內存繪制結果顯示在用戶界面上。

          而這個整個從生成繪制列表、光柵化、顯示的過程,就是我們常說的重繪的過程

          結語

          整個瀏覽器輸入 URL 到頁面渲染的過程涉及到的知識點非常廣,如 Chrome 多進程的架構、HTTP 通信過程、瀏覽器解析 JavaScript 過程、瀏覽器繪制頁面過程以及一些計算機的基礎知識等等,并且,這整個過程的分析其實和 Chrome-devtools 密切相關,所以很好的使用 Chrome-devtools 是非常重要的,后續應該會出一篇關于使用 Chrome-devtools 的指南。當然,本篇文章仍然存在諸多不足,歡迎提 issue ~


          作者:五柳
          鏈接:https://juejin.im/post/5e871ee56fb9a03c832b0013

          我們向瀏覽器的地址欄輸入URL的時候,網絡會進行一系列的操作,最終獲取到我們所需要的文件,如何交給瀏覽器進行渲染

          我們所關注的問題也就是:

          • 如何獲取到我們所需要的文件
          • 瀏覽器是如何渲染的

          大致的執行順序

          • URL解析
          • DNS 解析:緩存判斷 + 查詢IP地址
          • TCP 連接:TCP 三次握手
          • SSL/TLS四次握手(只有https才有這一步)
          • 瀏覽器發送請求
          • 服務器響應請求并返回數據
          • 瀏覽器解析渲染頁面
          • 斷開連接:TCP 四次揮手

          URL解析

          瀏覽器先會判斷輸入的字符是不是一個合法的URL結構,如果不是,瀏覽器會使用搜索引擎對這個字符串進行搜索

          URL結構組成

          https://www.example.com:80/path/to/myfile.html?key1=value1&key2=value2#anchor

          • 協議:https:// 互聯網支持多種協議,必須指明網址使用哪一種協議,默認是 HTTP 協議。 也就是說,如果省略協議,直接在瀏覽器地址欄輸入www.example.com,那么瀏覽器默認會訪問http://www.example.com。 HTTPS 是 HTTP 的加密版本,出于安全考慮,越來越多的網站使用這個協議。
          • 主機:www.example.com 主機(host)是資源所在的網站名或服務器的名字,又稱為域名。上例的主機是www.example.com。 有些主機沒有域名,只有 IP 地址,比如192.168.2.15。
          • 端口:https:// 同一個域名下面可能同時包含多個網站,它們之間通過端口(port)區分。 “端口”就是一個整數,可以簡單理解成,訪問者告訴服務器,想要訪問哪一個網站。 默認端口是80,如果省略了這個參數,服務器就會返回80端口的網站。 端口緊跟在域名后面,兩者之間使用冒號分隔,比如www.example.com:80。
          • 路徑:/path/to/myfile.html 路徑(path)是資源在網站的位置。比如,/path/index.html這個路徑,指向網站的/path子目錄下面的網頁文件index.html 互聯網的早期,路徑是真實存在的物理位置。現在由于服務器可以模擬這些位置,所以路徑只是虛擬位置 路徑可能只包含目錄,不包含文件名,比如/foo/,甚至結尾的斜杠都可以省略 這時,服務器通常會默認跳轉到該目錄里面的index.html文件(即等同于請求/foo/index.html),但也可能有其他的處理(比如列出目錄里面的所有文件),這取決于服務器的設置 一般來說,訪問www.example.com這個網址,很可能返回的是網頁文件www.example.com/index.html
          • 查詢參數:?key1=value1&key2=value2 查詢參數(parameter)是提供給服務器的額外信息。參數的位置是在路徑后面,兩者之間使用?分隔 查詢參數可以有一組或多組。每組參數都是鍵值對(key-value pair)的形式,同時具有鍵名(key)和鍵值(value),它們之間使用等號(=)連接。比如,key1=value就是一個鍵值對,key1是鍵名,value1是鍵值 多組參數之間使用&連接,比如key1=value1&key2=value2
          • 錨點:#anchor 錨點(anchor)是網頁內部的定位點,使用#加上錨點名稱,放在網址的最后,比如#anchor 瀏覽器加載頁面以后,會自動滾動到錨點所在的位置 錨點名稱通過網頁元素的id屬性命名

          DNS解析

          DNS(Domain Names System),域名系統,是互聯網一項服務,是進行域名和與之相對應的 IP 地址進行轉換的服務器

          第一步:緩存判斷

          判斷是正確的URL格式之后,DNS會在我們的緩存中查詢是否有當前域名的IP地址

          基本步驟:

          • 瀏覽器緩存:瀏覽器檢查是否在緩存中
          • 操作系統緩存:操作系統DNS緩存,去本地的hosts文件查找
          • 路由器緩存:路由器DNS緩存
          • ISP 緩存: ISP DNS緩存(ISP DNS 就是在客戶端電腦上設置的首選 DNS 服務器,又稱本地的DNS服務器)

          在經歷上述緩存查找還沒有找到的話,就進行下一步查詢操作

          第二步:查詢IP地址

          瀏覽器會去根域名服務器中查找,如果還沒有就去頂級域名服務器中查找,最后是權威域名服務器。

          找到IP地址后,將它記錄在緩存中,供下次使用。

          TCP連接:三次握手

          簡單理解

          簡單的理解就是:

          客戶端:hello,你好,你是server嗎?
          服務端:hello,你好,我是server,你是client嗎
          客戶端:yes,我是client
          開始數據傳輸.....
          ——————————————————————————————————————————
          客戶端(男人):我喜歡你,咱倆處對象吧
          服務端(女人):我也喜歡你,我答應你
          客戶端(男人):太棒了,我們現在去看電影吧
          開始數據傳輸.....
          


          然后雙方就正確建立連接,開始傳輸數據

          詳細分析

          • 第一次握手:客戶端發送一個帶 SYN=1,Seq=x 的數據包到服務器端口 第一次握手,由瀏覽器發起,告訴服務器我要發送請求了 SYN(synchronous):請求建立連接 seq(sequence):隨機序列號 請注意TCP規定SYN被設置為1的報文段不能攜帶數據但要消耗掉一個序號。
          • 第二次握手:服務器發回一個帶 SYN=1, ACK=1, seq=y, ack= x+1 的響應包以示傳達確認信息 第二次握手,由服務器發起,告訴客戶端我準備接受了,你趕緊發送吧 ACK(acknowledgement):確認,是一個確定字符 ack:ack=上一次的seq+1,作用是接受上一次遠端主機傳來的seq,加一然后再傳給客戶端,提示客戶端已經成功接收上一次所有數據 請注意這個報文段不能攜帶數據,因為它是SYN被設置為一的報文段但同樣要消耗掉一個序號
          • 第三次握手:客戶端再回傳一個帶 ACK,seq = x + 1, ack = y + 1 的數據包,代表“握手結束” 第三次握手,由瀏覽器發送,告訴服務器,我馬上就發了,準備接受吧 確認報文段可以攜帶數據。但如果不攜帶數據則不消耗序號,在這種情況下所發送的下一個數據報文段的序號仍是x + 1

          然后雙方就正確建立連接,開始傳輸數據

          SSL/TLS四次握手

          HTTPS 建立連接的過程,先進行 TCP 三次握手,再進行 TLS 四次握手(僅對https)

          因為 HTTPS 都是基于 TCP 傳輸協議實現的,得先建立完可靠的 TCP 連接才能做 TLS 握手的事情。

          第一次握手:客戶端發出請求Client Hello

          • 首先,客戶端先向服務器發出加密通信的請求,這被叫做clienthello請求。
          • 在這一步,客戶端主要向服務器提供以下信息:
            • 支持的協議版本,比如TLS1.0版本
            • 支持的加密方法,比如RSA公鑰加密
            • 一個客戶端生成的隨機數(client random), 稍后用于生成對話密鑰(session key)

          第二次握手:服務器回應Server Hello

          • 服務器收到客戶端請求后,向客戶端發出回應,這叫做serverhello
          • 這一步服務器主要干三件事:
            • 確認使用的加密通信協議版本,比如TLS1.00版本。如果游覽器與服務器支持的版本不一致,服務器關閉加密通信
            • 確認使用的加密方法(客戶端所支持),比如RSA公鑰加密
            • 將服務器證書、非對稱加密的公鑰,以及一個隨機數(Server random)發送給客戶端游覽器

          客戶端驗證證書

          客戶端收到服務器回應以后,首先驗證服務器證書,驗證手段就是執行如下三種檢查:

          • 檢查證書是否已過期;
          • 檢查證書中的域名與實際域名是否一致
          • 檢查證書是否是可信機構頒布的

          如果,上述過程中有任何一個環節發現問題,那么瀏覽器就會向訪問者顯示一個警告,由其選擇是否還要繼續通信。如果證書受信任,或者是用戶接受了不受信的證書,瀏覽器會生成一串新的隨機數(Premaster secret )

          第三次握手:客戶端回應

          此時,瀏覽器會根據前三次握手中的三個隨機數:

          • Client random
          • Server random
          • Premaster secret

          通過一定的算法來生成 “會話密鑰” (Session Key),這個會話密鑰就是接下來雙方進行對稱加密解密使用的密鑰!

          第四次握手:服務端回應

          服務端收到客戶端的回復,利用已知的加密解密方式進行解密,服務器收到客戶端的第三個隨機數( Premaster secret) 之后,使用同樣的算法計算出 “會話密鑰” (Session Key)。

          至此,整個握手階段全部結束。接下來,客戶端與服務器進入加密通信,就完全是使用普通的 HTTP 協議,只不過用 “會話密鑰” 加密內容。(非對稱加密解密將不再使用,接下來完全由對稱加密接手了,因為密鑰已經安全的傳送給了通信的雙方)

          總結

          1. 客戶端請求建立SSL鏈接,并向服務發送一個隨機數–Client random客戶端支持的加密方法,比如RSA公鑰加密,此時是明文傳輸
          2. 服務端回復一種客戶端支持的加密方法一個隨機數–Server random、授信的服務器證書非對稱加密的公鑰
          3. 客戶端收到服務端的回復后利用服務端的公鑰,加上新的隨機數–Premaster secret 通過服務端下發的公鑰及加密方法進行加密,發送給服務器
          4. 服務端收到客戶端的回復,利用已知的加解密方式進行解密,同時利用Client random、Server random和Premaster secret通過一定的算法生成HTTP鏈接數據傳輸的對稱加密key – session key

          瀏覽器發送請求

          連接建立成功之后,瀏覽器向服務器發送HTTP請求報文,來獲取自己想要的數據

          請求報文由請求行、請求頭、空行、請求體四部分組成

          • 請求行:有請求方法、請求的url、http協議及其版本
          • 請求頭:把瀏覽器的一些基礎信息告訴服務器。比如包含了瀏覽器所使用的操作系統、瀏覽器內核等信息,以及當前請求的域名信息、瀏覽器端的 Cookie 信息等
          • 空行:最后一個請求頭之后是一個空行,發送回車符和換行符,通知服務器以下不再有請求頭
          • 請求體(報文主體/請求中文):當使用POST, PUT等方法時,通常需要客戶端向服務器傳遞數據。這些數據就儲存在請求正文中。在請求包頭中有一些與請求正文相關的信息,例如: 現在的Web應用通常采用Rest架構,請求的數據格式一般為json。這時就需要設置Content-Type: application/json。

          服務端響應請求并返回數據

          服務器對http請求報文進行解析,并給客戶端發送HTTP響應報文對其進行響應

          HTTP響應報文也是由狀態行、響應頭、空行、響應體四部分組成

          • 響應行/狀態行:由 HTTP 版本協議字段、狀態碼和狀態碼的描述文本 3 個部分組成
          • 響應頭:用于指示客戶端如何處理響應體,告訴瀏覽器響應的類型、字符編碼和字節大小等信息
          • 空行:最后一個響應頭部之后是一個空行,發送回車符和換行符,通知服務器以下不再有響應頭部。
          • 響應體:返回客戶端所需數據

          這個時候瀏覽器拿到我們服務器返回的HTML文件,可以開始解析渲染頁面

          瀏覽器解析渲染頁面

          當我們經歷了上述的一系列步驟之后,我們的瀏覽器就拿到了我的HTML文件那么它又是如何解析整個頁面并且最終呈現出我們的網頁呢?

          渲染流程圖

          簡圖:從這張圖上我們可以得出一個重要的結論:下載CSS文件并不會阻塞HTML的解析

          詳圖

          詳細解析步驟

          解析一:HTML解析過程

          默認情況下服務器會給瀏覽器返回index.html文件,所以解析HTML是所有步驟的開始:解析HTML,會 構建DOM Tree

          當遇到我們的script文件的時候,我們是不能進行去構建DOM Tree的。它會停止繼續構建,首先下載JavaScript代碼,并且執行JavaScript的腳本,只有等到JavaScript腳本執行結束后,才會繼續解析HTML,構建DOM樹

          具體的相關細節看下面的script與頁面解析

          解析二:生成CSS規則

          • 在解析的過程中,如果遇到CSS的link元素,那么會由瀏覽器負責下載對應的CSS文件
            • 注意:下載CSS文件是不會影響DOM的解析的
          • 瀏覽器下載完CSS文件后,就會對CSS文件進行解析,解析出對應的規則樹
            • 我們可以稱之為 CSSOM (CSS Object Model,CSS對象模型

          解析三:構建Render Tree

          當有了 DOM Tree 和 CSSOM Tree 后,就可以兩個結合來 構建 Render Tree

          • 注意一:link元素不會阻塞DOM Tree的構建過程,但是會阻塞Render Tree的構建過程
            • 因為Render Tree在構建時,需要對應的CSSOM Tree。當我們DOMTree解析完成的時候,如果CSSOM Tree沒解析完成就會阻塞。當然一般情況下瀏覽器會進行優化處理,不會傻傻的等待
          • 注意二:Render Tree和DOMTree并不是一一對應的關系
            • 比如對于display為none的元素,壓根不會出現在render tree中

          解析四:布局(layout)和繪制(Paint)

          • 第四步是在**渲染樹(Render Tree)**上運行 布局(Layout) 以計算每個節點的幾何體。 渲染樹會 表示 要顯示哪些節點以及其他樣式,但是 不表示 每個節點的尺寸、位置 等信息 布局的主要目的是為了確定呈現樹中所有節點的寬度、高度和位置信息
          • 第五步是將每個節點 繪制(Paint) 到屏幕上 在繪制階段,瀏覽器將布局階段計算的 每個frame轉為屏幕上實際的像素點 包括 將元素的可見部分進行繪制,比如文本、顏色、邊框、陰影、替換元素(比如img)

          特殊解析:composite合成

          • 繪制的過程,可以將布局后的元素繪制到多個合成圖層中【這是瀏覽器的一種優化手段】
          • 默認情況下,標準流中的內容都是被繪制在同一個圖層(Layer)中的
          • 而一些特殊的屬性,會創建一個新的合成層(Compositinglayer ),并且新的圖層可以利用GPU來加速繪制
            • 因為每個合成層都是單獨渲染的
          • 那么哪些屬性可以形成新的合成層呢?常見的一些屬性:
            • 3D transforms
            • video、canvas、iframe
            • opacity動畫轉換時
            • position: fixed
            • will-change:一個實驗性的屬性,提前告訴瀏覽器元素可能發生哪些變化
            • animation或 transition設置了opacity、transform
          • 分層確實可以提高性能,但是它以內存管理為代價,因此不應作為web性能優化策略的一部分過度使用

          其他相關概念

          回流

          • 回流reflow(也可以稱之為重排) 第一次確定節點的大小和位置,稱之為布局(layout) 之后對節點的大小、位置修改 重新計算 稱之為回流
          • 什么情況下引起回流呢? 比如DOM結構發生改變(添加新的節點或者移除節點) 比如改變了布局(修改了width、height、padding、font-size等值) 比如窗口resize(修改了窗口的尺寸等) 比如調用getComputedStyle方法獲取尺寸、位置信息

          重繪

          • 重繪repaint【字面理解就是對頁面再做繪制】 第一次渲染內容稱之為繪制(paint) 之后重新渲染稱之為重繪
          • 什么情況下會引起重繪呢? 比如修改背景色、文字顏色、邊框顏色、樣式等

          聯系

          • 回流一定會引起重繪,所以回流是一件很消耗性能的事情。
          • 所以在開發中要盡量避免發生回流 修改樣式時盡量一次性修改【比如通過cssText修改,比如通過添加class修改】 盡量 避免頻繁的操作DOM【我們可以在一個DocumentFragment或者父元素中將要操作的DOM操作完成,再一次性的操作】 盡量 避免通過getComputedStyle獲取尺寸、位置等信息 對某些元素使用position的absolute或者fixed【并不是不會引起回流,而是開銷相對較小,不會對其他元素造成影響】

          script元素

          script元素和頁面聯系

          • 我們現在已經知道了頁面的渲染過程,但是JavaScript在哪里呢? 事實上,瀏覽器在解析HTML的過程中,遇到了 script元素是不能繼續構建DOM樹的 它會 停止繼續構建,首先下載JavaScript代碼,并且執行JavaScript的腳本 只有 等到JavaScript腳本執行結束后,才會繼續解析HTML,構建DOM樹
          • 為什么要這樣做呢? 這是 因為JavaScript的作用之一就是操作DOM,并且可以修改DOM 如果我們等到DOM樹構建完成并且渲染再執行JavaScript會造成嚴重的回流和重繪,影響頁面的性能 所以會在遇到script元素時,優先下載和執行JavaScript代碼,再繼續構建DOM樹
          • 但是這個也往往會帶來新的問題,特別是現代頁面開發中: 在目前的開發模式中(比如Vue、React),腳本往往比HTML頁面更“重”,處理時間需要更長 所以會造成頁面的解析阻塞,在腳本下載、執行完成之前,用戶在界面上什么都看不到
          • 為了解決這個問題,script元素給我們提供了兩個屬性(attribute) : defer和async

          defer屬性

          • defer屬性告訴瀏覽器 不要等待腳本下載,而繼續解析HTML,構建DOM Tree
            • 腳本會 由瀏覽器來進行下載,但是不會阻塞DOM Tree的構建過程
            • 如果腳本提前下載好了,它會 等待DOM Tree構建完成,在DOMContentLoaded事件之前先執行defer中的代碼
          • 所以DOMContentLoaded總是會等待defer中的代碼先執行完成
          <script src="./foo.js" defer></script>
          <script>
          	 window.addEventListener("DOMContentLoaded",()=>{
                   console.log("DOMContentLoaded");
               })
          </script>
          • 多個帶defer的腳步是可以保持正確的執行順序的
          • 從某種角度來說,defer可以提高頁面的性能,并且推薦放到head元素中
          • 注意:defer僅適用于外部腳本,對于script默認內容會被忽略

          async屬性

          • async特性與defer有些類似,它也能夠讓腳本不阻塞頁面
          • async是讓一個腳本完全獨立的:
            • 瀏覽器 不會因async 腳本而阻塞(與defer類似)
            • async腳本不能保證順序,它是獨立下載、獨立運行,不會等待其他腳本
            • async不會能保證在DOMContentLoaded之前或者之后執行
          • defer通常用于需要在文檔解析后操作DOM的JavaScript代碼,并且對多個script文件有順序要求的
          • async通常用于獨立的腳本,對其他腳本,甚至DOM沒有依賴的

          斷開連接:TCP 四次揮手

          在渲染完成后,瀏覽器可能會繼續加載頁面中的其他資源,如異步加載的內容或者通過JavaScript生成的動態內容。

          而在此過程中,如果沒有其他資源需要加載,瀏覽器將與服務器之間的TCP連接斷開。

          簡單理解

          復制代碼主動方:我已經關閉了向你那邊的主動通道了,這是我最后一次給你發消息了,之后只能被動接收你的信息了
          被動方:收到你通道關閉的信息
          被動方:那我也告訴你,我這邊向你的主動通道也關閉了
          主動方:最后收到你關閉的信息,OK結束
          斷開連接,結束通訊
          ————————————————————————————————————————————————————————————————————————————
          提出分手的可能是男生(客戶端),也可能是女生(服務端)
          主動方:分手吧,我不喜歡你了!
          被動方:行,你等我忙完手上的工作我在收拾你!
          被動方:我忙完了,分手就分手!
          主動方:好,好聚好散,拜拜!
          斷開連接,結束通訊
          

          詳細分析

          由于TCP連接是全雙工的,因此,每個方向都必須要單獨進行關閉,這一原則是當一方完成數據發送任務后,發送一個FIN來終止這一方向的連接,收到一個FIN只是意味著這一方向上沒有數據流動了,即不會再收到數據了,但是在這個TCP連接上仍然能夠發送數據,直到這一方向也發送了FIN。首先進行關閉的一方將執行主動關閉,而另一方則執行被動關閉。

          任何一方都可以在數據傳送結束后發出連接釋放的通知,所有主動發起關閉請求可以是客戶端,也可以是服務端

          這里我們假設是由客戶端先主動發起關閉請求

          • 第一次揮手:TCP客戶進程會發送TCP連接釋放報文段,并進入終止等待1(FIN-WAIT-1)狀態。 FIN:終止位,表示斷開TCP連接 TCP規定終止位FIN等于1的報文段即使不攜帶數據,也要消耗掉一個序號
          • 第二次揮手:TCP服務器進程收到TCP連接釋放報文段后,會發送一個普通的TCP確認報文段并進入關閉等待(CLOSE-WAIT)狀態。 序號seq字段的值設置為v,與之前收到的TCP連接釋放報文段中的確認號匹配 TCP客戶進程收到TCP確認報文段后就進入終止等待2(FIN-WAIT-2)狀態,等待TCP服務器進程發出的TCP連接釋放報文段 這時的TCP連接屬于半關閉狀態,也就是TCP客戶進程已經沒有數據要發送了。但如果TCP服務器進程還有數據要發送,TCP客戶進程仍要接收,也就是說從TCP服務器進程到TCP客戶進程這個方向的連接并未關閉,這個狀態可能要持續一段時間。
          • 第三次揮手:TCP服務器進程發送TCP連接釋放報文段 假定序號seq字段的值為w,這是因為在半關閉狀態下,TCP服務器進程可能又發送了一些數據。 確認號ack字段的值為u+1,這是對之前收到的TCP連接釋放報文段的重復確認。
          • 第四次揮手:TCP服務器進程收到確定報文段后就進入關閉狀態,而TCP客戶進程還要經過2MSL后才能進入關閉狀態。

          之后斷開連接,結束通訊

          總結

          • 瀏覽器先判斷是否為合法的url格式,不合法則在搜索引擎中搜索
          • 合法后,DNS解析會先判斷緩存中是否有url的ip地址。
          • 緩存的查詢順序是:瀏覽器緩存 -> 操作系統緩存(本地的hosts文件) -> 路由器緩存 -> 本地的DNS服務器緩存
          • 在緩存中沒有的情況,則向服務器發起請求查詢ip地址。
          • 查詢IP地址的順序是:根域名服務器 -> 頂級域名服務器 -> 權威域名服務器。直到查找到返回,并將其存儲在緩存中下次使用
          • TSP建立連接,也就是三次握手
          • 第一次握手,攜帶建立連接請求SYN=1和隨機序列seq=x
          • 第二次握手,攜帶確定字段ACK=1、連接請求SYN=1、隨機序列seq=y和ack為上一次握手的seq+1,就是x+1
          • 第三次握手,攜帶確定字段ACK=1、ack=y+1、seq=x+1
          • 如果是https,還有一個TLS四次握手
          • 第一次握手,客戶端向服務端發送 支持的協議版本 + 支持的加密方法 + 生成的隨機數
          • 第二次握手,服務端向客戶端發送 證書 + 公鑰 + 隨機數
          • 第三次握手前,客戶端會先驗證證書有沒有過期、域名對不對、是否可信機構頒發的。
          • 沒有問題或者用戶接受不受信的證書,瀏覽器會生成一個新的隨機數
          • 第三次握手,將之前的三個隨機數通過一定的算法生成會話秘鑰,之后的加密解密都是用這個秘鑰
          • 第四次握手,服務端收到回復,是用確定的加密方法進行解密,得到第三個隨機數,使用同樣的算法計算出會話秘鑰
          • 建立連接之后,瀏覽器發送http請求
          • 請求報文由請求行、請求頭、空行和請求體組成
          • 服務器解析請求報文,返回響應報文
          • 響應報文由響應行、響應頭、空行和響應體組成,我們需要的html文件就在響應體中
          • 瀏覽器拿到html文件并開始解析,構建dom tree。遇到css文件,下載并構建CSSOM tree。等到兩者都構建完成之后,一起構建Render tree。然后進行布局和繪制
          • 其中遇到了script標簽,則停止構建dom tree,等下載完成之后才會繼續構建dom tree
          • 當資源傳輸完畢之后,TSP關閉連接,進行四次揮手的操作,其中四次揮手的操作客戶端和服務器都可以發起
          • 第一次揮手,攜帶斷開連接的FIN=1、確定字段ACK=1、隨機序列seq=u,ack=v
          • 第二次揮手,攜帶確定字段ACK=1、隨機序列seq=v,ack=u+1
          • 第三次揮手,攜帶確定字段ACK=1、斷開連接FIN=1、隨機序列seq=w、ack=u+1
          • 第四次揮手,攜帶確定字段ACK=1,隨機序列seq=u+1,ack=w+1
          • 等待2MSL后進入關閉狀態
          • 斷開連接,結束通訊


          作者:前端實習生鯨落
          鏈接:https://juejin.cn/post/7279093851000242234


          主站蜘蛛池模板: 中文日韩字幕一区在线观看| 日本一区精品久久久久影院| 亚洲国产一区明星换脸| 国产香蕉一区二区精品视频| 制服丝袜一区二区三区| 精品视频一区二区观看| 亚洲精品日韩一区二区小说| 久久精品国产第一区二区| 国产一区二区三区露脸| 国产91大片精品一区在线观看| 国产99精品一区二区三区免费| 久久精品无码一区二区三区免费 | 男人的天堂亚洲一区二区三区| 末成年女A∨片一区二区| 人妻无码一区二区视频| 精品国产一区二区三区色欲| 久久亚洲日韩精品一区二区三区| 国精品无码一区二区三区在线蜜臀| 国产在线一区二区| 成人区人妻精品一区二区不卡网站| 无人码一区二区三区视频| 91国偷自产一区二区三区| 日韩精品一区二区三区不卡 | 精品日产一区二区三区手机| 久久久久女教师免费一区| 91精品福利一区二区三区野战| 久久精品一区二区三区日韩| 成人精品视频一区二区三区| 久久无码人妻精品一区二区三区| 国产精品第一区第27页| 国产高清精品一区| 国产成人精品一区二三区在线观看 | 久久精品成人一区二区三区| 精品一区二区三区波多野结衣| 亚洲一区二区中文| 无码夜色一区二区三区| 精品国产高清自在线一区二区三区| 日韩福利视频一区| 国产伦精品一区二区三区视频金莲| 免费无码一区二区| 国产成人精品无码一区二区三区|