单页应用的原理从早起的根据url的hash变化,到根据H5的history的变化,实现无刷新条件下的页面从新渲染。那么在单页应用中是如何监听url的变化呢,本文将总结一下,如何在单页页面中优雅的监听url的变化。html
- 单页应用原理
- 监听url中的hash变化
- 监听经过history来改变url的事件
- replaceState和pushState行为的监听
原文在个人博客中:github.com/fortheallli…前端
欢迎starhtml5
单页应用的原理,在咱们的上一篇文章中React-Router源码阅读已经讲的很详细,这里作一个简单介绍。单页应用使得页面能够在无刷新的条件下从新渲染,经过hash或者html5 Bom对象中的history能够作到改变url,可是不刷新页面。git
早期的前端路由是经过hash来实现的:github
改变url的hash值是不会刷新页面的。浏览器
所以能够经过hash来实现前端路由,从而实现无刷新的效果。hash属性位于location对象中,在当前页面中,能够经过:app
window.location.hash='edit'
复制代码
来实现改变当前url的hash值。执行上述的hash赋值后,页面的url发生改变。函数
赋值前:http://localhost:3000 赋值后:http://localhost:3000/#editthis
在url中多了以#结尾的hash值,可是赋值先后虽然页面的hash值改变致使页面完整的url发生了改变,可是页面是不会刷新的。url
此外,除了能够经过window.location.hash来改变当前页面的hash值外,还能够经过html的a标签来实现:
<a href="#edit">edit</a>
复制代码
HTML5的History接口,History对象是一个底层接口,不继承于任何的接口。History接口容许咱们操做浏览器会话历史记录。
History提供了一些属性和方法。
History的属性:
History方法:
History.back(): 返回浏览器会话历史中的上一页,跟浏览器的回退按钮功能相同
History.forward():指向浏览器会话历史中的下一页,跟浏览器的前进按钮相同
History.go(): 能够跳转到浏览器会话历史中的指定的某一个记录页
History.pushState():pushState能够将给定的数据压入到浏览器会话历史栈中,该方法接收3个参数,对象,title和一串url。pushState后会改变当前页面url,可是不会伴随着刷新
History.replaceState():replaceState将当前的会话页面的url替换成指定的数据,replaceState后也会改变当前页面的url,可是也不会刷新页面。
上面的方法中,pushState和repalce的相同点:
就是都会改变当前页面显示的url,但都不会刷新页面。
不一样点:
pushState是压入浏览器的会话历史栈中,会使得History.length加1,而replaceState是替换当前的这条会话历史,所以不会增长History.length.
经过改变hash值,或者history的repalceState和pushState均可以实现无刷新的改变url。这样还留有一个问题须要解决:
如何监听url的改变
由于咱们不只要无刷新的改变url,还要监听到这个url改变的行为,根据该行为去从新渲染视图。在下几章中,重点介绍一下如何监听url的改变。
经过hash改变了url,会触发hashchange事件,只要监听hashchange事件,就能捕获到经过hash改变url的行为。
window.onhashchange=function(event){
console.log(event);
}
//或者
window.addEventListener('hashchange',function(event){
console.log(event);
})
复制代码
当hash值改变时,输出一个HashChangeEvent。该HashChangeEvent的具体值为:
{isTrusted: true, oldURL: "http://localhost:3000/", newURL: "http://localhost:3000/#teg", type: "hashchange".....}
复制代码
有了监听事件,且改变hash页面不刷新,这样咱们就能够在监听事件的回调函数中,执行咱们展现和隐藏不一样UI显示的功能,从而实现前端路由。
在上一章讲到了经过History改变url有如下几种方法:History.back()、History.forward()、History.go()、History.pushState()和History.replaceState()。
同时在history中还支持一个事件,该事件为popstate。第一想法就是若是popstate可以监听全部的history方法所致使的url变化,那么就大功告成了。遗憾的是:
History.back()、History.forward()、History.go()事件是会触发popstate事件的,可是History.pushState()和History.replaceState()不会触发popstate事件。
若是是History.back(),History.forward()、History.go()那么会触发popstate事件,咱们只须要:
window.addEventListener('popstate', function(event) {
console.log(event);
})
复制代码
就能够监听到相应的行为,手动调用:
window.history.go();
window.history.back();
window.history.forward();
复制代码
都会触发这个事件,此外,在浏览器中点击后退和前进按钮也会触发popstate事件,这个事件内容为:
PopStateEvent {isTrusted: true, state: null, type: "popstate", target: Window, currentTarget: Window, …}
复制代码
可是,History.pushState()和History.replaceState()不会触发popstate事件,举例来讲:
window.addEventListener('popstate', function(event) {
console.log(event);
})
window.history.pushState({first:'first'}, "page 2", "/first"})
复制代码
上述例子中不会有任何的输出,由于并无监听的popstate事件的发生。
可是History.go和History.back()等,虽然能够触发popstate事件,可是都会刷新页面,咱们在单页应用中使用的是replaceState和pushState,所以这里还有一个等待解决的问题:
如何监听replaceState和pushState行为
在上面的例子中咱们发现History.replaceState和pushState确实不会触发popstate事件,那么如何监听这两个行为呢。能够经过在方法里面主动的去触发popState事件。另外一种就是在方法中建立一个新的全局事件。
具体作法为:
var _wr = function(type) {
var orig = history[type];
return function() {
var rv = orig.apply(this, arguments);
var e = new Event(type);
e.arguments = arguments;
window.dispatchEvent(e);
return rv;
};
};
history.pushState = _wr('pushState');
history.replaceState = _wr('replaceState');
复制代码
这样就建立了2个全新的事件,事件名为pushState和replaceState,咱们就能够在全局监听:
window.addEventListener('replaceState', function(e) {
console.log('THEY DID IT AGAIN! replaceState 111111');
});
window.addEventListener('pushState', function(e) {
console.log('THEY DID IT AGAIN! pushState 2222222');
});
复制代码
这样就能够监听到pushState和replaceState行为。