单页面应用history路由实现原理
在单页面应用中history路由是很受欢迎的,它的路由显示方式和传统的路由方式相同,在显示上很美观,比hash的方式看着舒服的多。我们经常使用的api比如push或pushState,replace或replaceState,go,forward,back等等,其实都是和html5内置的history对象息息相关的,其原理就是调用了HTML5的history内置对象,然后进行了一些封装操作。
history对象属性
window 对象通过 history 对象提供了对浏览器的回话历史的访问(不要与 WebExtensions history搞混了,我们要说的这个history变化时是不会请求服务器的)。它暴露了很多有用的方法和属性,允许你在用户浏览历史中向前和向后跳转,同时——从HTML5开始——提供了对history栈中内容的操作。
history对象提供的API 可以实现无刷新更改地址栏链接,配合 AJAX 可以做到无刷新跳转,所以通过history进行路由变化是不会向服务器进行请求的
向前向后跳转
这两个api很简单,只是一条语句就好
history.back()//向后跳转
history.forward()//向前跳转
跳转到指定的点
此api调用方式为:
history.go(1)
go的参数为你要跳转到的url相对当前url的位置标志。向后跳转是负数,向前是正数。
这个api我个人觉得用处不是很大,因为我们操作url要么就是直接向后跳转,要么直接向前跳转,要么就是直接赋值url直接跳转过去。该api需要传入相对的位置标志就会显得略有些麻烦。
跳转到指定的页面
HTML5引入了 history.pushState() 和 history.replaceState() 方法,这两个方法都可以跳转到指定的url页面,主要区别在于replaceState() 是修改了当前的历史记录项而不是新建一个,也就是说history.pushState()之后,history.length会加一,但是replaceState()却不会。 注意这并不会阻止其在全局浏览器历史记录中创建一个新的历史记录项。
我们先来说history.pushState(),其调用方式如下:
history.pushState(state, title, url)
pushState() 需要三个参数: 一个状态对象, 一个标题 (目前被忽略), 和 (可选的) 一个URL. 让我们来解释下这三个参数详细内容:
- 状态对象: 状态对象state是一个JavaScript对象,通过pushState () 创建新的历史记录条目。无论什么时候用户导航到新的状态,popstate事件就会被触发,能触发popstate事件的是history.back()或history.forword()以及history.go(),pushState()是不会触发的,后面会介绍如何监听pushState事件。该事件的state属性包含该历史记录条目状态对象的副本。
- 标题:Firefox目前忽略这个参数,但未来可能会用到。传递一个空字符串在这里是安全的,而在将来这是不安全的。二选一的话,你可以为跳转的state传递一个短标题。
- URL:该参数定义了新的历史URL记录。注意,调用 pushState() 后浏览器并不会立即加载这个URL,但可能会在稍后某些情况下加载这个URL,比如在用户重新打开浏览器时。新URL不必须为绝对路径。如果新URL是相对路径,那么它将被作为相对于当前URL处理。新URL必须与当前URL同源,否则 pushState() 会抛出一个异常。该参数是可选的,缺省为当前URL。
使用示例
history.pushState({msg:"跳转url"}, null, '/oecom');
调用完成以后,如果之前的url为https://www.oecom.cn/history , 那么调用完成以后就会变为:https://www.oecom.cn/oecom
然后我们再来说一下history.replaceState(),他的参数和pushState的参数相同,区别上面我们也说过了,下面我们来看一下具体是什么样子。
history.pushState({msg:"跳转url"}, null, '/oecom1');
history.pushState({msg:"跳转url"}, null, '/oecom2');
history.replaceState({msg:"跳转url"}, null, '/oecom3');
我们调用三次url跳转,前两次使用pushState,后一次使用replaceState,当我们在调用history.back()时,会直接跳转到oecom1路由上,原因就在于replaceState是直接将当前路由替换掉,而不是增加一个。
popstate事件
每当活动的历史记录项发生变化时, popstate 事件都会被传递给window对象。如果当前活动的历史记录项是被 pushState 创建的,或者是由 replaceState 改变的,那么 popstate 事件的状态属性 state 会包含一个当前历史记录状态对象的拷贝
window.onpopstate = function(event) {
alert("location: " + document.location + ", state: " + JSON.stringify(event.state));
};
//绑定事件处理函数.
history.pushState({page: 1}, "title 1", "?page=1"); //添加并激活一个历史记录条目 https://www.oecom.cn/example.html?page=1,条目索引为1
history.pushState({page: 2}, "title 2", "?page=2"); //添加并激活一个历史记录条目 https://www.oecom.cn/example.html?page=2,条目索引为2
history.replaceState({page: 3}, "title 3", "?page=3"); //修改当前激活的历史记录条目 https://www.oecom.cn/example.html?page=2 变为 https://www.oecom.cn/example.html?page=3,条目索引为3
history.back(); // 弹出 "location: https://www.oecom.cn/example.html?page=1, state: {"page":1}"
history.back(); // 弹出 "location: https://www.oecom.cn/example.html, state: null
history.go(2); // 弹出 "location: https://www.oecom.cn/example.html?page=3, state: {"page":3}
history当前状态
页面加载时,或许会有个非null的状态对象。这是有可能发生的,举个例子,假如页面(通过pushState() 或 replaceState() 方法)设置了状态对象而后用户重启了浏览器。那么当页面重新加载时,页面会接收一个onload事件,但没有 popstate 事件。然而,假如你读取了history.state属性,你将会得到如同popstate 被触发时能得到的状态对象。
你可以读取当前历史记录项的状态对象state,而不必等待popstate 事件, 只需要这样使用history.state 属性:
let currentState = history.state;
上面我们说了popstate事件,这个事件无法监听pushState和replaceState事件,有一个很笨的方式就是采用setInterval轮询的方式来判断history.state是否变化来判断url是否变化,当然这个方法是很消耗性能的。
我们可以采用改写一下pushState方法来实现,思路是在history添加一个onpushState属性,在pushState时进行调用即可。
(function(history){
var pushState = history.pushState;
history.pushState = function(state) {
if (typeof history.onpushstate == "function") {
history.onpushstate({state: state});
}
return pushState.apply(history, arguments);
};
})(window.history);
//设置其方法和popstate相同即可
window.onpopstate =history.onpushstate= function(event) {
alert("location: " + document.location + ", state: " + JSON.stringify(event.state));
};
如果要监听replaceState,方法和此方法相同,在此不再赘述。
浏览器支持
Chrome | Safari | Firefox | Opera | IE | Android | iOS |
---|---|---|---|---|---|---|
31+ | 7.1+ | 34+ | 11.50+ | 10+ | 4.3+ | 7.1+ |
支持,哈哈