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小时内与您取得联系。