1. URI和URL
与URI相比,我们更熟悉URL。URL正是使用web浏览器等访问web页面时需要输入的网页地址。比如我们经常输入的 https://www.baidu.com/
就是 URL
1.1 统一资源标识符
URI是 Uniform Resource Identifier
的缩写。RFC2396分别对这三个单词进行了以下定义:
Uniform
规定统一的格式可以方便处理多种不同类型的资源,而不用根据上下文环境来识别资源指定的访问方式。
Resopurce
可标识的任何资源。不仅是文档文件,图像或者服务等能够区别于其他类型的,全部可作为资源,它可以是多个对象的集合体。
Identifier
标识可标识的对象,也可以称为标识符。
综上所述,URI就是由某个协议方案表示的资源的定位标识符。洗衣方案是指访问资源时所使用的协议类型名称。HTTP就是协议的一种方案,除此之外,还有ftp、file、TELNET等30
多种标准URI协议方案。
协议方案由互联网号码分配局管理颁布。URI用字符标识某一互联网资源,而URL表示资源的地点,可见URL是URI的子集。
1.1.1 URI的通用语法
URI的通用语法由 5
个组件组成:
URI = scheme:[//authority]path[?query][#fragment]
其中,authority
组件可以由以下3个组件组成:
authority = [userinfo@]host[:post]
- 在
authority
中,userinfo
作为登陆欣欣,通常形式为指定用户名和密码,当从服务器端获取资源时作为身份人中凭证使用,userinfo
为可选项。 - 服务器地址
host
在使用绝对路径URI时需指定访问的服务器地址,地址可用为被DNS
解析的域名,或者是IPv4
地址以及IPv6
地址。 post
为服务器连接的网络端口号,作为可选项,如果不指定,则自动使用默认的端口号。path
为带层次的文件路径,指定服务器上的文件路径,以访问特定的资源。query
为查询字符串,针对指定路径的文件资源,可使用查询字符串传入任意查询参数。
统一资源标识符 URI
通用语法中列举了几种 URI
例子,如下所示:
ftp://ftp.is.co.za//rfc/rfc1808.txt
http://www.itef.org/rfc/rfc2396.txt
ldap://[2001.db8::7]/c=GB?objectClass?one
mailto:John.Doe@example.com
news:comp.infosystems.www.servers.unix
tel:+1-826-555-1212
telnet://192.0.2.16a:18/
urn:oasis:names:specification:docbook:dxt:xml:4.1.2
1.1.2 URL的通用语法
统一资源定位器URL作为URI的一种,如同网络的门牌,表示了一个互联网资源的 地址
,如 https://www.baidu.con
表示通过 HTTP
协议从主机名为 www.baidu.com
的主机上获取首页资源。
URL的语法定义与URI一致:
URL = scheme:[//authority]path[?query][#fragment]
以 https://www.baidu.com:80/foo/baz?title=moment
为例,其中 https
表示加密的超文本传输协议,www.baidu.com
为服务器的地址,80
为服务器上的都安口号,foo/baz
为资源路径,?title=moment
为路径的查询,以 "?"
开头,各个参数是以 "&"
为分割,以等号 "="
分开参数名称与数据。
1.2.1 URI和URL总结
URL是一种具体的URI,它是URI的一个子集,它不仅唯一标识资源,而且还提供了定位该资源的信息。URI是一种语义上的抽象概念,可以是绝对的,也可以是相对的,而URL则必须提供足够的信息来定位,是绝对的。
总的来说,人的身份证号就是URI,通过身份证号可以让我们能找到具体的一个人。而通过具体的地址去查找某个人,例如广东省广州市天河区某某大学一栋101宿舍的云梦泽,通过具体的地址也可以找到某个人,也起到了URI的作用,所以URL是URI的子集。URL是描述某一资源的具体位置。
2. 浏览器记录
浏览器记录是浏览器中各个页面用户的导航记录。在现代浏览器中,浏览器记录并没有直接的 API
可获取,其可通过 window.history.length
获取当前记录栈的长度信息。浏览器记由浏览器统一管理,并不属某个具体的页面,与页面形式及其内存均无关系。
window.history
对象上存在着诸多属性,通过 lib.dom.d.ts
文件中查看History有以下定义:
interface History {readonly length: number;scrollRestoration:"auto" | "manual";readonly state: any;back(): void;forward(): void;go(delta?: number): void;pushState(data: any, unused: string, url?: string | URL | null): void;replaceState(data: any, unused: string, url?: string | URL | null): void;
}
2.1.1 history.pushState
在 HTML 文档中,history.pushState()
方法向当前浏览器会话的历史堆栈中添加一个状态(state
)。
history.pushState
方法作为 HTML5
特性的一部分,目前被广泛使用。history.pushState
用于无刷w新增历史栈记录,调用 history.pushState
方法可改变浏览器路径。
pushState() 需要三个参数: 一个状态对象, 一个标题 (目前被忽略), 和一个可选的URL.详情可看 MDN文档。其基本语法如下所示:
pushState(state:Object, title:string,[url:string]):undefined
当设置第三个参数URL时,可改变浏览器URL,且不会刷新浏览器。
history.pushState(null, null, "/foo/bar");
console.log(location.pathname); // /foo/bar
如果URL中包含 Unicode
字符,则浏览器也会将字符按 UTF-8
编码。
history.pushState(null, null, "/中文");
console.log(location.pathname); // /%E4%B8%AD%E6%96%87
通过以下代码示例通过设置state
,title
和url
创建一条新的历史记录:
// index.html文件
<body><script> const state = { nickname: "moment", age: 7 };const title = "";const url = "title.html";history.pushState(state, title, url); </script></body>// title.html文件<body><h1>这个是title页面</h1><div class="age"></div><div class="nickname"></div><script> const nickname = document.querySelector(".nickname");const age = document.querySelector(".age");nickname.innerHTML = window.history.state.nickname;age.innerHTML = window.history.state.age; </script></body>
上例代码通过 pushState
改变浏览器URL,并传进了 state
参数,页面跳转到 title.html
的页面,在 title.html
文件中通过 history.state
获取传进来的数据并通过 innerHTNL
展示出来,结果如下所示:
因为历史栈由浏览器统一管理,不属于某个具体页面,并不存在于页面的内存中,所以历史栈在刷新页面后不会丢失,栈中记录的各 state
对象也为持久化存储,在导航过程中也不会丢失。
histiry.pushState
使用结构化拷贝算法进行序列化存储,会将拷贝后的结果记录在历史栈记录中。结构化拷贝算法除了能拷贝基本类型,还能考悲剧更多的对象类型。相比 JSON
的序列化,这样的序列化更为安全,如循环引用的对象,结构化序列的手段将会序列化成功,而 JSON
的序列化将会报错,原因在于结构化蓄力的手段保存了每一个访问过的对象的记录,遇到复制过的对象会进行跳过。
结构化拷贝算法要注意特殊的场景,如果 history.pushState
的 state
对象中有 dom
节点、error
对象、function
函数等,调用 history.pushState
方法会抛出异常,且对某些对象的特定属性,如 regExp
的lastIndex
、Object
对象的 setter
和 getter
等,结构化拷贝的过程都会丢失。
2.1.2 历史栈变化
history.pushState
的调用会引起历史栈的变化,浏览器通常会维护一个用户访问过的历史栈,以便用户进行导航。用户通常可以通过浏览器的前进和后退按钮或者调用 window.history.go
等方法在历史栈中进行移动,可理解为下图所示的栈指针,不改变历史栈的内容,栈内的记录数量不会发生改变:
当调用 history.pushState
方法时,历史栈的内容会被修改,行为表现为添加历史栈的栈记录,如上图所示,url和state会被推到栈顶。历史栈会有一个指针指向栈的其中一个内容,栈指针所指向的内容正是浏览器当前所运行的url所对应的页面。
当调用 history.pushState({a:3},null,'/c')
方法后,则栈记录加1,栈指针也会指向最新的栈记录位置。通过 history.length
能查看当前历史栈的长度。
浏览器提供了两个 API
接口用以调用浏览器本身自带的返回和向前,它们分别是 back()
方法和 forward()
方法。其中 back()
方法会在会话历史记录中向后移动一页。如果没有上一页,则此方法调用不执行任何操作。而在会话历史中向前移动一页。它与使用delta
参数为 1 时调用 history.go(delta)
的效果相同。 还有一个 go
方法从会话历史记录中加载特定页面,你可以使用它在历史记录中前后移动,具体取决于 delta
参数的值,具体语法如下:
window.history.go(delta);
delta
参数可选,相当于当前页面你要去往历史页面的位置。负值表示向后移动,正值表示向前移动。因此,例如:history.go(2)
向前移动两页,history.go(-2)
则向后移动两页。如果未向该函数传参或delta
相等于0,则该函数与调用 location,reload()
具有相同的效果。具体有以下示例:
// 向后移动一页 back()
window.history.go(-1);
// 向前移动一页 forward()
window.history.go(1);
// 向后移动两页
window.history.go(-2);
// 向前移动两页
window.history.go(2);
2.1.3 history.replaceState
history.replaceState
的用法与 history.pushState
非常相似,区别在于 history.replaceState
将修改当前的历史记录想而不是新建一个,其语法为:
history.replaceState(stateObj, title[, url]);
history.replaceState
不会改变历史栈中记录的数量,它只会更新当前栈的信息,历史栈的长度不会发生变化。
2.1.4 通过相对路径太黏和修改浏览器记录
history.replaceState
与 history.pushState
,除支持绝对路径导航外,还支持相对路径导航:如:
window.history.pushState(null, null, "../one");
window.history.replaceState(null, null, "./one.two");
它们的规则和在 Node,js
中的 url.resolve()
,具体可参考 Node.js官方文档 ,两者相对路径的解决规则一致。对于相对路径导航,其遵循以下规则:
- 如果路径以
"/"
开头,则会替换掉整个路径; - 如果路径不以
"/"
开头,则会得到相对当前URI地址的路径(在浏览器无base元素的存在的情况下),跟你混路径解决规则会替换URL
地址中的最后一级目录,即最后一个"/"
分隔符后缪按的路径部分,如:
// 当前路径为 /one/two/three
console.log(location.pathname); // /one/two/three
window.history.pushState(null, null, "four");
console.log(location.pathname); // /one/two/four
如果当前路径的最后一个字符为 "/"
,则可以认为 "/"
后紧接空字符串,执行相对路径导航会替换空字符串部分。
如果路径中含有 "."
".."
,则表示当前路径及上一级路径。
// 当前路径为 /one/two/three
console.log(location.pathname); // /one/two/three
window.history.pushState(null, null, "./four");
console.log(location.pathname); // /one/two/four
// 当前级路径改为 five 下一级路径改为 six
window.history.pushState(null, null, "./five/././six");
console.log(location.pathname); // /one/two/five/six
对于 ".."
操作符,其表明回到上一句路径。
// 当前路径为 /one/two/three
console.log(location.pathname); // /one/two/three
window.history.pushState(null, null, "../four");
console.log(location.pathname); // /one/four
// 当前路径为 /one
console.log(location.pathname); // /one
window.history.pushState(null, null, "../five/six");
console.log(location.pathname); // /five/six
3. Location
Location
接口表示其链接到的对象的位置(URL)。所做的修改反映在与之相关的对象上。 Document
和 Window
接口都有这样一个链接的 Location
,分别通过 Document.location
和Window.location
访问。其中 Document.location
和Window.location
指向同一个对象:
console.log(window.location === document.location); // true
通过 lib.dom.d.ts
文件查看 Location
的类型定义,主要有以下属性和方法:
interface Location {hash: string;host: string;hostname: string;href: string;toString(): string;readonly origin: string;pathname: string;port: string;protocol: string;search: string;assign(url: string | URL): void;reload(): void;replace(url: string | URL): void;
}
3.1.1 hash
Location
接口的 hash
属性返回一个 USVString
,其中会包含 URL
标识中的 '#'
和后面 URL
片段标识符。这里 fragment
不会经过百分比编码(URL 编码)。如果 URL
中没有 fragment
,该属性会包含一个空字符串,“”。
<a id="myAnchor" href="/moment.com#elegance">Examples</a>
<script> const link = document.querySelector("a");console.log(link.hash);
</script
如果设置的 location.hash
值与浏览器的 URL
地址的 hash
值相同,就不会触发任何事件,也不会添加任何历史记录。或者如果前后两次对 location.hash
设置了相同的值,则第一次 locationn.hash
设置生效,第二次相同的设置不会产生任何事件和历史记录。
3.1.2 host
Location
接口的 host
属性是包含了主机的一段 USVString
,其中包含:主机名,如果 URL
的端口号是非空的,还会跟上一个 ':'
,最后是 URL
的端口号。
console.log(location.host);
// 该输出结果在 create-react-app创建的项目中输出的是 localhost:3000
// 在 live Server 打开的文件打开的html文件输出的 127.0.0.1:5500
// 而在普通方式打开的html文件中输出的空字符串
而 hostname
属性则不携带端口号。
3.1.3 href
Location
接口的 href
属性是一个字符串化转换器 (stringifier), 返回一个包含了完整 URL
的 USVString
值,且允许 href
的更新。
<a id="myAnchor" href="/moment.com#elegance">Examples</a>
<script> const link = document.querySelector("a");console.log(link.href); // http://127.0.0.1:5500/moment.com#elegance </script>
3.1.4 replace
与 history.replaceState
类似,window.location.replace
会替换当前的栈记录,但在设置绝对路径中,这意味着用户将不能用 "后退"
按钮再次回到旧页面。
location.href = "https://www.foo.com";
location.href = "https://www.bar.com";
location.replace("https://www.baz.com");
在上面的例子中,由于在 www.bar.com
中调用了 location.href
方法,www.bar.com
的页面记录将替换为 https://www.baz.com
的页面记录。当用户在 https://www.baz.com
页面单机浏览器 "后退"
按钮是,将回到 www.foo.com
页面。
4. 浏览器相关事件
4.1.1 popstate事件
每当激活同一文档中不同的历史记录条目时,popstate
事件就会在对应的 window
对象上触发。如果当前处于激活状态的历史记录条目是由 history.pushState()
方法创建的或者是由 history.replaceState()
方法修改的,则 popstate
事件的 state
属性包含了这个历史记录条目的 state
对象的一个拷贝。
调用 history.replaceState()
和 history.pushState()
不会触发 popstate
事件。当移动栈指针或单机浏览器的 "前进"
或 "后退"
按钮时,将触发 popstate
事件,可通过 window.addEventListener
监听该事件。
举个例子🌰,有以下的历史栈,当前历史栈指针正指向 two.html
页面:
我们通过以下代码来监听 popstate
事件的变化:
window.addEventListener("popstate", function (event) {console.log(event);
});
我们通过点击 two.html
页面的前进按钮,前往 three.html
的页面,控制台会有以下输出:
而回退到 one.html
页面又有以下输出,也正是我们所预料到的:
history.state
同步的是记录栈中的值,每次导航都会获得新的 state
对象。栈记录中的 state
对象是深拷贝存储在浏览器中的,无论在浏览器进行导航,还是刷新当前页面或是关闭浏览器页签再恢复,历史栈的内容都存在且不会被销毁。
当前后两次设置相同的 location.hash
值时,不会触发两次 popstate
事件。若通过 location.href
设置 hash
值,则无论前后设置的值是否相同,都会触发 popstate
事件。当前后两次设置的值相同时,只添加一个历史栈。
4.2.1 hashchange 事件
hashchange
事件用于监听浏览器值的 hash
值变化,其监听方式为:
window.addEventListener("hashchange", function (event) {console.log(event);
});
通过下列代码来演示一下在 hashchange
事件的事件相应函数中,可以获取独享 HashChangeEvent
,具体有以下属性和方法:
通过上面的动图可以看出,hashchange
事件可以通过设置 location.hash
、在地址栏手动修改 hash
、调用 window.history.go
、在浏览器中单击 "前进"
或 "后退"
按钮等方式触发。且每次事件都会得到当前的 url
和旧的 url
。
值得注意的是,pushState
不会触发 hashChange
事件,即使前后当行的 URL
仅 hash
部分发生改变,也是如此。
最后
最近还整理一份JavaScript与ES的笔记,一共25个重要的知识点,对每个知识点都进行了讲解和分析。能帮你快速掌握JavaScript与ES的相关知识,提升工作效率。
有需要的小伙伴,可以点击下方卡片领取,无偿分享