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 多進程架構。因為,從瀏覽器輸入 URL 到頁面渲染的整個過程都是由 Chrome 架構中的各個進程之間的配合完成。
Chrome 的多進程架構:
發生這個過程的前提,用戶在地址欄中輸入了 URL,而地址欄會根據用戶輸入,做出如下判斷:
在網絡進程接收到 URL 后,并不是馬上對指定 URL 進行請求。首先,我們需要進行 DNS 解析域名得到對應的 IP,然后通過 ARP 解析 IP 得到對應的 MAC(Media Access Control Address)地址。
域名是我們取代記憶復雜的 IP 的一種解決方案,而 IP 地址才是目標在網絡中所被分配的節點。MAC 地址是對應目標網卡所在的固定地址。
1. DNS 解析
而 DNS 解析域名的過程分為以下幾個步驟:
2. 通信過程
首先,建立 TCP 連接,即三次握手過程:
然后,利用 TCP 通道進行數據傳輸:
最后,斷開 TCP 連接,即四次握手過程:
而這整個過程的客戶端則是網絡進程。并且,在數據傳輸的過程還可能會發生的重定向的情況,即當網絡進程接收到狀態碼為 3xx 的響應報文,則會根據響應報文首部字段中的 Location 字段的值進行重新向,即會重新發起請求
3. 數據處理
當網絡進程接收到的響應報文狀態碼,進行相應的操作。例如狀態碼為 200 OK 時,會解析響應報文中的 Content-Type 首部字段,例如我們這個過程 Content-Type 會出現 application/javascript、text/css、text/html,即對應 Javascript 文件、CSS 文件、HTML 文件。
詳細的 MIME 類型講解可以看 MDN
當前需要渲染 HTML 時,則需要創建渲染進程,用于后期渲染 HTML。而對于渲染進程,如果是同一站點是可以共享一個渲染進程,例如 a.abc.com 和 c.abc.com 可以共享一個渲染渲染進程。否則,需要重新創建渲染進程
需要注意的是,同站指的是頂級域名和二級域名相等
在創建完渲染進程后,網絡進程會將接收到的 HTML、JavaScript 等數據傳遞給渲染進程。而在渲染進程接收完數據后,此時用戶界面上會發生這幾件事:
大家都知道頁面渲染的過程也是面試中單獨會考的點,并且時常會由這個點延申出另一個問題,即如何避免回流和重繪。
渲染過程,是整個從理器輸入 URL 到頁面渲染過程的最后一步。而頁面渲染的過程可以分為 9 個步驟:
由于網絡進程傳輸給渲染進程的是 HTML 字符串,所以,渲染進程需要將 HTML 字符串轉化成 DOM 樹。例如:
需要注意的是這個 DOM 樹不同于 Chrome-devtool 中 Element 選項卡的 DOM 樹,它是存在內存中的,用于提供 JavaScript 對 DOM 的操作。
構建 CSSOM 的過程,即通過解析 CSS 文件、style 標簽、行內 style 等,生成 CSSOM。而這個過程會做這幾件事:
CSS Object Model 是一組允許用 JavaScript 操縱 CSS 的 API。詳細 API 講解可以看 MDN
通常情況下,在構建 DOM 樹或 CSSOM 的同時,如果也要加載 JavaScript,則會造成前者的構建的暫停。當然,我們可以通過 defer 或 sync 來實現異步加載 JavaScript。雖然 defer 和 sync 都可以實現異步加載 JavaScript,但是前者是在加載后,等待 CSSOM 和 DOM 樹構建完后才執行 JavaScript,而后者是在異步加載完馬上執行,即使用 sync 的方式仍然會造成阻塞。
而 JavaScript 執行的過程,即編譯和運行 JavaScript 的過程。由于 JavaScript 是解釋型的語言。所以這個過程會是這樣的:
在有了 DOM 樹和 CSSOM 之后,需要將兩者結合生成渲染樹 Render Tree,并且這個過程會去除掉那些 display: node 的節點。此時,渲染樹就具備元素和元素的樣式信息。
根據 Render Tree 渲染樹,對樹中每個節點進行計算,確定每個節點在頁面中的寬度、高度和位置。
需要注意的是,第一次確定節點的大小和位置的過程稱為布局,而第二次才被稱為回流
由于層疊上下文的存在,渲染引擎會為具備層疊上下文的元素創建對應的圖層,而諸多圖層的疊加就形成了我們看到的一些頁面效果。例如,一些 3D 的效果、動畫就是基于圖層而形成的。
值得一提的是,對于內容溢出存在滾輪的情況也會進行分層
對于存在圖層的頁面部分,需要進行有序的繪制,而對于這個過程,渲染引擎會將一個個圖層的繪制拆分成繪制指令,并按照圖層繪制順序形成一個繪制列表。
有了繪制列表后,渲染引擎中的合成線程會根據當前視口的大小將圖層進行分塊處理,然后合成線程會對視口附近的圖塊生成位圖,即光柵化。而渲染進程也維護了一個柵格化的線程池,專門用于將圖塊轉為位圖。
柵格化的過程通常會使用 GPU 加速,例如使用 wil-change、opacity,就會通過 GPU 加速顯示
當所有的圖塊都經過柵格化處理后,渲染引擎中的合成線程會生成繪制圖塊的指令,提交給瀏覽器進程。然后瀏覽器進程將頁面繪制到內存中。最后將內存繪制結果顯示在用戶界面上。
而這個整個從生成繪制列表、光柵化、顯示的過程,就是我們常說的重繪的過程
整個瀏覽器輸入 URL 到頁面渲染的過程涉及到的知識點非常廣,如 Chrome 多進程的架構、HTTP 通信過程、瀏覽器解析 JavaScript 過程、瀏覽器繪制頁面過程以及一些計算機的基礎知識等等,并且,這整個過程的分析其實和 Chrome-devtools 密切相關,所以很好的使用 Chrome-devtools 是非常重要的,后續應該會出一篇關于使用 Chrome-devtools 的指南。當然,本篇文章仍然存在諸多不足,歡迎提 issue ~
作者:五柳
鏈接:https://juejin.im/post/5e871ee56fb9a03c832b0013
我們向瀏覽器的地址欄輸入URL的時候,網絡會進行一系列的操作,最終獲取到我們所需要的文件,如何交給瀏覽器進行渲染
我們所關注的問題也就是:
瀏覽器先會判斷輸入的字符是不是一個合法的URL結構,如果不是,瀏覽器會使用搜索引擎對這個字符串進行搜索
URL結構組成
https://www.example.com:80/path/to/myfile.html?key1=value1&key2=value2#anchor
DNS(Domain Names System),域名系統,是互聯網一項服務,是進行域名和與之相對應的 IP 地址進行轉換的服務器
判斷是正確的URL格式之后,DNS會在我們的緩存中查詢是否有當前域名的IP地址
基本步驟:
在經歷上述緩存查找還沒有找到的話,就進行下一步查詢操作
瀏覽器會去根域名服務器中查找,如果還沒有就去頂級域名服務器中查找,最后是權威域名服務器。
找到IP地址后,將它記錄在緩存中,供下次使用。
簡單的理解就是:
客戶端:hello,你好,你是server嗎?
服務端:hello,你好,我是server,你是client嗎
客戶端:yes,我是client
開始數據傳輸.....
——————————————————————————————————————————
客戶端(男人):我喜歡你,咱倆處對象吧
服務端(女人):我也喜歡你,我答應你
客戶端(男人):太棒了,我們現在去看電影吧
開始數據傳輸.....
然后雙方就正確建立連接,開始傳輸數據
然后雙方就正確建立連接,開始傳輸數據
HTTPS 建立連接的過程,先進行 TCP 三次握手,再進行 TLS 四次握手(僅對https)
因為 HTTPS 都是基于 TCP 傳輸協議實現的,得先建立完可靠的 TCP 連接才能做 TLS 握手的事情。
客戶端收到服務器回應以后,首先驗證服務器證書,驗證手段就是執行如下三種檢查:
如果,上述過程中有任何一個環節發現問題,那么瀏覽器就會向訪問者顯示一個警告,由其選擇是否還要繼續通信。如果證書受信任,或者是用戶接受了不受信的證書,瀏覽器會生成一串新的隨機數(Premaster secret )。
此時,瀏覽器會根據前三次握手中的三個隨機數:
通過一定的算法來生成 “會話密鑰” (Session Key),這個會話密鑰就是接下來雙方進行對稱加密解密使用的密鑰!
服務端收到客戶端的回復,利用已知的加密解密方式進行解密,服務器收到客戶端的第三個隨機數( Premaster secret) 之后,使用同樣的算法計算出 “會話密鑰” (Session Key)。
至此,整個握手階段全部結束。接下來,客戶端與服務器進入加密通信,就完全是使用普通的 HTTP 協議,只不過用 “會話密鑰” 加密內容。(非對稱加密解密將不再使用,接下來完全由對稱加密接手了,因為密鑰已經安全的傳送給了通信的雙方)
連接建立成功之后,瀏覽器向服務器發送HTTP請求報文,來獲取自己想要的數據
請求報文由請求行、請求頭、空行、請求體四部分組成
服務器對http請求報文進行解析,并給客戶端發送HTTP響應報文對其進行響應
HTTP響應報文也是由狀態行、響應頭、空行、響應體四部分組成
這個時候瀏覽器拿到我們服務器返回的HTML文件,可以開始解析渲染頁面
當我們經歷了上述的一系列步驟之后,我們的瀏覽器就拿到了我的HTML文件那么它又是如何解析整個頁面并且最終呈現出我們的網頁呢?
簡圖:從這張圖上我們可以得出一個重要的結論:下載CSS文件并不會阻塞HTML的解析
詳圖:
默認情況下服務器會給瀏覽器返回index.html文件,所以解析HTML是所有步驟的開始:解析HTML,會 構建DOM Tree
當遇到我們的script文件的時候,我們是不能進行去構建DOM Tree的。它會停止繼續構建,首先下載JavaScript代碼,并且執行JavaScript的腳本,只有等到JavaScript腳本執行結束后,才會繼續解析HTML,構建DOM樹。
具體的相關細節看下面的script與頁面解析
當有了 DOM Tree 和 CSSOM Tree 后,就可以兩個結合來 構建 Render Tree
<script src="./foo.js" defer></script>
<script>
window.addEventListener("DOMContentLoaded",()=>{
console.log("DOMContentLoaded");
})
</script>
在渲染完成后,瀏覽器可能會繼續加載頁面中的其他資源,如異步加載的內容或者通過JavaScript生成的動態內容。
而在此過程中,如果沒有其他資源需要加載,瀏覽器將與服務器之間的TCP連接斷開。
復制代碼主動方:我已經關閉了向你那邊的主動通道了,這是我最后一次給你發消息了,之后只能被動接收你的信息了
被動方:收到你通道關閉的信息
被動方:那我也告訴你,我這邊向你的主動通道也關閉了
主動方:最后收到你關閉的信息,OK結束
斷開連接,結束通訊
————————————————————————————————————————————————————————————————————————————
提出分手的可能是男生(客戶端),也可能是女生(服務端)
主動方:分手吧,我不喜歡你了!
被動方:行,你等我忙完手上的工作我在收拾你!
被動方:我忙完了,分手就分手!
主動方:好,好聚好散,拜拜!
斷開連接,結束通訊
由于TCP連接是全雙工的,因此,每個方向都必須要單獨進行關閉,這一原則是當一方完成數據發送任務后,發送一個FIN來終止這一方向的連接,收到一個FIN只是意味著這一方向上沒有數據流動了,即不會再收到數據了,但是在這個TCP連接上仍然能夠發送數據,直到這一方向也發送了FIN。首先進行關閉的一方將執行主動關閉,而另一方則執行被動關閉。
任何一方都可以在數據傳送結束后發出連接釋放的通知,所有主動發起關閉請求可以是客戶端,也可以是服務端
這里我們假設是由客戶端先主動發起關閉請求
之后斷開連接,結束通訊
作者:前端實習生鯨落
鏈接:https://juejin.cn/post/7279093851000242234
*請認真填寫需求信息,我們會在24小時內與您取得聯系。