内容简介:在深入了解 History API 之前,我们需要讨论一下前端路由;路由指的是通过不同 URL 展示不同页面或者内容的功能,这个概念最初是由后端提出的,因此,在传统的 Web 开发模式中,路由都是服务器来控制和管理的。既然已经有了后端路由,为什么还需要前端路由呢?我们知道跳转页面实际上就是为了展示那个页面的内容,那么无论是选择 AJAX 异步的方式获取数据还是将页面内容保存在本地,都是为了让页面之间的交互不必每次都刷新页面,这样用户体验会有极大的提升,也就能被称为那么前端路由是怎样解决改变页面内容的同时改变
在深入了解 History API 之前,我们需要讨论一下前端路由;路由指的是通过不同 URL 展示不同页面或者内容的功能,这个概念最初是由后端提出的,因此,在传统的 Web 开发模式中,路由都是服务器来控制和管理的。
既然已经有了后端路由,为什么还需要前端路由呢?我们知道跳转页面实际上就是为了展示那个页面的内容,那么无论是选择 AJAX 异步的方式获取数据还是将页面内容保存在本地,都是为了让页面之间的交互不必每次都刷新页面,这样用户体验会有极大的提升,也就能被称为 SPA (单页面应用)了;但是,不够完美,因为这种场景下缺少路由功能,所以会导致用户多次获取页面之后,不小心刷新当前页面,会直接退回到页面的 初始状态 ,用户体验极差。
那么前端路由是怎样解决改变页面内容的同时改变 URL 并保持页面不刷新呢?这就引出了我们这篇文章的主题: History API 。
History API
DOM window
对象通过 history
对象提供了对 当前会话 (标签页或者 frame
)浏览历史的访问,在 HTML4 的时候我们已经能够操纵浏览历史向前或向后跳转了;当时,我们能够使用的属性和方法有下面这些:
-
window.history.length
:返回当前会话浏览过的页面数量。 -
window.history.go(?delta)
:接受一个整数作为参数,按照当前页面在会话浏览历史记录中的位置为基准进行移动。如果参数为 0 或 undefined、null、false,将刷新页面,相当于执行window.location.reload()
。如果在运行这个方法的过程中,发现移动后会超出会话浏览历史记录的边界时,将没有任何效果,并且也不会报错。 -
window.history.back()
:移动到上一页,相当于点击浏览器的后退按钮,等价于window.history.go(-1)
。 -
window.history.forward()
:移动到下一页,相当于点击浏览器的前进按钮,等价于window.history.go(1)
。
window.history.back()
和 window.history.forward()
就是通过 window.history.go(?delta)
实现的,因此,如果没有上一页或者下一页,那表示会超出边界,所以它们的处理方式和 window.history.go(?delta)
是一样的。
HTML4 的时候并没有能够改变 URL 的 API;但是,从 HTML5 开始,History API 新增了操作会话浏览历史记录的功能。以下是新增的属性和方法:
-
window.history.state
:这个参数是只读的,表示与会话浏览历史的当前记录相关联的状态对象。 -
window.history.pushState(data, title, ?url)
:在会话浏览历史记录中添加一条记录。以下是方法的参数详情:-
data
(状态对象):是一个能被序列化的任何东西,例如 object、array、string、null 等。为了方便用户重新载入时使用,状态对象会在序列化之后保存在本地;此外, 序列化之后 的状态对象根据浏览器的不同有不一样的大小限制(注意:规范 并没有说需要限制大小),如果超出,将会抛出异常。 -
title
(页面标题):当前所有的浏览器都会忽略这个参数,因此可以置为空字符串。 -
url
(页面地址):如果新的 URL 不是绝对路径,那么将会相对于当前 URL 处理;并且,新的 URL 必须与当前 URL 同源 ,否则将抛出错误。另外,该参数是可选的,默认为当前页面地址。
-
-
window.history.replaceState(data, title, ?url)
:与window.history.pushState(data, title, ?url)
类似,区别在于replaceState
将修改会话浏览历史的当前记录,而不是新增一条记录;但是,需要注意:调用replaceState
方法还是会在 全局 浏览历史记录中创建新记录 。
调用 pushState
和 replaceState
方法之后,地址栏会更改 URL,却不会立即加载新的页面,等到用户重新载入时,才会真正进行加载。因此, 同源的目的 是为了防止恶意代码让用户以为自己处于另一个页面。
popstate
事件
每当用户导航会话浏览历史的记录时,就会触发 popstate
事件;例如,用户点击浏览器的倒退和前进按钮;当然这些操作在 JavaScript 中也有对应的 window.history.back()
、 window.history.forward()
和 window.history.go(?delta)
方法能够达到同样的效果。
如果导航到的记录是由 window.history.pushState(data, title, ?url)
创建或者 window.history.replaceState(data, title, ?url)
修改的,那么 popstate
事件对象的 state
属性将包含导航到的记录的状态对象的一个 拷贝 。
另外,如果用户在地址栏中 手动 修改 hash
或者通过写入 window.location.hash
的方式来 模拟用户 行为,那么也会触发 popstate
事件,并且还会在会话浏览历史中新增一条记录。需要注意的是,在调用 window.history.pushState(data, title, ?url)
时,如果 url
参数中有 hash
,并不会触发这一条规则;因为我们要知道, pushState
只是导致会话浏览历史的记录发生变化,让地址栏有所反应,并不是 用户导航 或者通过脚本来 模拟用户 的行为。
获取当前状态对象
在介绍 HTML5 中 history
对象新增的属性和方法时,有说道 window.history.state
属性,通过它我们也能得到 popstate
事件触发时获取的状态对象。
在用户重新载入页面时, popstate
事件并不会触发,因此,想要获取会话浏览历史的当前记录的状态对象,只能通过 window.history.state
属性。
Location 对象
Location 对象提供了 URL 相关的信息和操作方法,通过 document.location
和 window.location
属性都能访问这个对象。
History API 和 Location 对象实际上是通过地址栏中的 URL 关联 的,因为 Location 对象的值始终与地址栏中的 URL 保持一致,所以当我们操作会话浏览历史的记录时,Location 对象也会随之更改;当然,我们修改 Location 对象,也会触发浏览器执行相应操作并且改变地址栏中的 URL。
属性
Location 对象提供以下属性:
-
window.location.href
:完整的 URL;http://username:password@www.test.com:8080/test/index.html?id=1&name=test#test
。 -
window.location.protocol
:当前 URL 的协议,包括:
;http:
。 -
window.location.host
:主机名和端口号,如果端口号是80
(http)或者443
(https),那就会省略端口号,因此只会包含主机名;www.test.com:8080
。 -
window.location.hostname
:主机名;www.test.com
。 -
window.location.port
:端口号;8080
。 -
window.location.pathname
:URL 的路径部分,从/
开始;/test/index.html
。 -
window.location.search
:查询参数,从?
开始;?id=1&name=test
。 -
window.location.hash
:片段标识符,从#
开始;#test
。 -
window.location.username
:域名前的用户名;username
。 -
window.location.password
:域名前的密码;password
。 -
window.location.origin
:只读,包含 URL 的协议、主机名和端口号;http://username:password@www.test.com:8080
。
除了 window.location.origin
之外,其他属性都是可读写的;因此,改变属性的值能让页面做出相应变化。例如对 window.location.href
写入新的 URL,浏览器就会立即跳转到相应页面;另外,改变 window.location
也能达到同样的效果。
// window.location = 'https://www.example.com'; window.location.href = 'https://www.example.com'; 复制代码
需要注意的是,如果想要在同一标签页下的不同 frame
(例如父窗口和子窗口)之间 跨域 改写 URL,那么只能通过 window.location.href
属性,其他的属性写入都会抛出跨域错误。
改变 hash
改变 hash
并不会触发页面跳转,因为 hash
链接的是当前页面中的某个片段,所以如果 hash
有变化,那么页面将会滚动到 hash
所链接的位置;当然,页面中如果 不存在 hash
对应的片段,则没有 任何效果 。这和 window.history.pushState(data, title, ?url)
方法非常类似,都能在不刷新页面的情况下更改 URL;因此,我们也可以使用 hash
来实现前端路由,但是 hash
相比 pushState
来说有以下缺点:
-
hash
只能修改 URL 的片段标识符部分,并且必须从#
开始;而pushState
却能修改路径、查询参数和片段标识符;因此,在新增会话浏览历史的记录时,pushState
比起hash
来说更符合以前后端路由的访问方式,也更加优雅。// hash http://www.example.com/#/example // pushState http://www.example.com/example 复制代码
-
hash
必须与原先的值不同,才能新增会话浏览历史的记录;而pushState
却能新增相同 URL 的记录。 -
hash
想为新增的会话浏览历史记录关联数据,只能通过字符串的形式放入 URL 中;而pushState
方法却能关联所有能被序列化的数据。 -
hash
不能修改页面标题,虽然pushState
现在设置的标题会被浏览器忽略,但是并不代表以后不会支持。
hashchange
事件
我们可以通过 hashchange
事件监听 hash
的变化,这个事件会在用户导航到有 hash
的记录时触发,它的事件对象将包含 hash
改变前的 oldURL
属性和 hash
改变后的 newURL
属性。
另外, hashchange
事件与 popstate
事件一样也不会通过 window.history.pushState(data, title, ?url)
触发。
方法
Location 对象提供以下方法:
-
window.location.assign(url)
方法接受一个 URL 字符串作为参数,使得浏览器立刻跳转到新的 URL。document.location.assign('http://www.example.com'); // or // document.location = 'http://www.example.com'; 复制代码
-
window.location.replace(url)
方法与window.location.assign(url)
实现一样的功能,区别在于replace
方法执行后跳转的 URL 会 覆盖 浏览历史中的当前记录,因此原先的当前记录就在浏览历史中 删除 了。 -
window.location.reload(boolean)
方法使得浏览器重新加载当前 URL。如果该方法没有接受值或值为false
,那么就相当于用户点击浏览器的刷新按钮,这将导致浏览器 拉取缓存 中的页面;当然,如果没有缓存,那就会像执行window.location.reload(true)
一样, 重新请求 页面。 -
window.location.toString()
方法返回整个 URL 字符串。window.location.toString(); // or // window.location.href; 复制代码
路由实现
在使用 History API 实现路由时,我们要注意这个 API 里的方法( pushState
和 replaceState
)在改变 URL 时,并不会触发事件;因此想要像 hash
一样 只通过 事件( hashchange
)实现路由是不太可能了。
既然如此,我们就需要知道哪些方式能够触发 URL 的更新了;在单页面应用中,URL 改变只能由下面三种情况引起:
- 点击浏览器的前进或后退按钮。
- 点击
a
标签。 - 调用
pushState
或者replaceState
方法。
对于用户手动点击浏览器的前进或后退按钮的操作,通过监听 popstate
事件,我们就能知道 URL 是否改变了;点击 a
标签实际上也是调用了 pushState
或者 replaceState
方法,只不过因为 a
标签会有 默认行为 ,所以需要阻止它,以避免进行跳转。
<!-- index.html --> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>前端路由实现</title> <style> .link { color: #00f; cursor: pointer; } .link:hover { text-decoration: underline; } </style> </head> <body> <ul> <li><a class="link" data-href="/111">111</a></li> <li><a class="link" data-href="/222">222</a></li> <li><a class="link" data-href="/333">333</a></li> </ul> <div id="content"></div> <script src="./router.js"></script> <script> // 创建实例 const router = new Router(); const contentDOM = document.querySelector('#content'); // 注册路由 router.route('/111', state => { contentDOM.innerHTML = '111'; }); router.route('/222', state => { contentDOM.innerHTML = '222'; }); router.route('/333', state => { contentDOM.innerHTML = '333'; }); </script> </body> </html> 复制代码
// router.js const noop = () => undefined; class Router { constructor() { this.init(); } // 初始化 init() { this.routes = {}; this.listen(); this.bindLink(); } // 全部的监听事件 listen() { window.addEventListener('DOMContentLoaded', this.listenEventInstance.bind(this)); window.addEventListener('popstate', this.listenEventInstance.bind(this)); } unlisten() { window.removeEventListener('DOMContentLoaded', this.listenEventInstance); window.removeEventListener('popstate', this.listenEventInstance); } // 监听事件后,触发路由的回调 listenEventInstance() { this.trigger(this.getCurrentPathname()); }; getCurrentPathname() { return window.location.pathname; } // 注册路由 route(pathname, callback = noop) { this.routes[pathname] = callback; } // 触发回调 trigger(pathname) { if (!this.routes[pathname]) { return; } const {state} = window.history; this.routes[pathname](state); } // 绑定 a 标签,阻止默认行为 bindLink() { document.addEventListener('click', e => { const {target} = e; const {nodeName, dataset: {href}} = target; if (!nodeName === 'A' || !href) { return; } e.preventDefault(); window.history.pushState(null, '', href); this.trigger(href); }); } } 复制代码
生成 Router
的实例时,我们需要做以下工作:
- 初始化路由映射;这个映射实际上就是一个对象,
key
是路径名,value
是触发的回调。 - 监听
popstate
和DOMContentLoaded
事件;在上文我们已经知道popstate
事件在页面加载时并不会触发,因此需要监听DOMContentLoaded
事件来触发初始的 URL 的回调。 - 绑定全部
a
标签,以便我们在阻止默认行为之后,能够调用pushState
或replaceState
方法来更新 URL,并触发回调。
注册路由其实上就是在 路由映射对象 中为 路径 绑定 回调 ,因为 URL
改变后会执行回调,所以我们可以在回调中改变内容;这样一个很简单的前端路由就实现了。
以上所述就是小编给大家介绍的《【深入吧,HTML 5】 性能 & 集成 —— History API》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 深入剖析Swift性能优化
- 深入分析 Spark UDF 的性能
- mysql性能优化1:深入认识索引
- 【基本功】深入剖析Swift性能优化
- 《深入理解计算机系统》之浅析程序性能优化
- 【深入吧,HTML 5】 性能 & 集成 —— Web Workers
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
精通Windows应用开发
[美] Jesse Liberty Philip Japikse Jon Galloway / 苏宝龙 / 人民邮电出版社 / 59.00元
Windows 8.1的出现不仅提供了跨设备的用户体验,也提供了跨设备的开发体验。本书着眼于实际项目中所需要的特性,以及现有C#编程知识的运用,对如何最大限度地利用Metro、WinRT和Windows 8进行了讲解,内容详尽,注重理论学习与实践开发的配合。 Windows 8.1和WinRT的作用及其特殊性 如何使用先进特性创建具有沉浸感和吸引力的Windows 8.1应用 如......一起来看看 《精通Windows应用开发》 这本书的介绍吧!