單頁應用的原理從早起的根據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行爲。