單頁應用的原理從早起的根據url的hash變化,到根據H5的history的變化,實現無刷新條件下的頁面從新渲染。那麼在單頁應用中是如何監聽url的變化呢,本文將總結一下,如何在單頁頁面中優雅的監聽url的變化。html
- 單頁應用原理
- 監聽url中的hash變化
- 監聽經過history來改變url的事件
- replaceState和pushState行爲的監聽
原文在個人博客中:https://github.com/forthealll...前端
歡迎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的屬性:
保存了會出發popState事件的方法,所傳遞過來的屬性對象(後面會在pushState和replaceState方法中詳細的介紹)
History方法:
上面的方法中,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行爲。