詳解JavaScript實現(xiàn)監(jiān)聽路由變化
前端實現(xiàn)路由變化主要有兩種方式,這兩種方式最大特點就是實現(xiàn)URL切換無刷新功能
- 通過hash改變,利用window.onhashchange 監(jiān)聽。
- 通過history的改變,進(jìn)行js操作加載頁面,然而history并不像hash那樣簡單,因為history的改變,除了瀏覽器的幾個前進(jìn)后退(使用 history.back(), history.forward()和 history.go() 方法來完成在用戶歷史記錄中向后和向前的跳轉(zhuǎn)。)等操作會主動觸發(fā)popstate 事件,pushState,replaceState 并不會觸發(fā)popstate事件。
history
主要來了解一下History
pushState()方法
需要三個參數(shù): 一個狀態(tài)對象, 一個標(biāo)題 (目前被忽略), 和 (可選的) 一個URL. 讓我們來解釋下這三個參數(shù)詳細(xì)內(nèi)容:
狀態(tài)對象(state object) — 狀態(tài)對象state是一個JavaScript對象,通過pushState () 創(chuàng)建新的歷史記錄條目。無論什么時候用戶導(dǎo)航到新的狀態(tài),popstate事件就會被觸發(fā),且該事件的state屬性包含該歷史記錄條目狀態(tài)對象的副本。 狀態(tài)對象可以是能被序列化的任何東西。原因在于Firefox將狀態(tài)對象保存在用戶的磁盤上,以便在用戶重啟瀏覽器時使用,我們規(guī)定了狀態(tài)對象在序列化表示后有640k的大小限制。如果你給 pushState() 方法傳了一個序列化后大于640k的狀態(tài)對象,該方法會拋出異常。如果你需要更大的空間,建議使用 sessionStorage 以及 localStorage.
標(biāo)題(title) — Firefox 目前忽略這個參數(shù),但未來可能會用到。在此處傳一個空字符串應(yīng)該可以安全的防范未來這個方法的更改。或者,你可以為跳轉(zhuǎn)的state傳遞一個短標(biāo)題。
URL — 該參數(shù)定義了新的歷史URL記錄。注意,調(diào)用pushState() 后瀏覽器并不會立即加載這個URL,但可能會在稍后某些情況下加載這個URL(不會加載該資源,但我們可以通過監(jiān)聽它的改變,來改變視圖,這就成為單頁面的路由實現(xiàn)的一個方式,實現(xiàn)頁面無刷新),比如在用戶重新打開瀏覽器時。新URL不必須為絕對路徑。如果新URL是相對路徑,那么它將被作為相對于當(dāng)前URL處理。新URL必須與當(dāng)前URL同源(同源策略),否則 pushState()會拋出一個異常。該參數(shù)是可選的,缺省為當(dāng)前URL。
在某種意義上,調(diào)用 pushState() 與 設(shè)置 window.location = “#foo” 類似,二者都會在當(dāng)前頁面創(chuàng)建并激活新的歷史記錄。但 pushState()具有如下幾條優(yōu)點:
新的 URL可以是與當(dāng)前URL同源的任意URL 。相反,只有在修改哈希時,設(shè)置 window.location 才能是同一個 document。
如果你不想改URL,就不用改。相反,設(shè)置 window.location = “#foo”;在當(dāng)前哈希不是 #foo 時, 才能創(chuàng)建新的歷史記錄項。
你可以將任意數(shù)據(jù)和新的歷史記錄項相關(guān)聯(lián)。而基于哈希的方式,要把所有相關(guān)數(shù)據(jù)編碼為短字符串。
如果 標(biāo)題 隨后還會被瀏覽器所用到,那么這個數(shù)據(jù)是可以被使用的(哈希則不是)。
注意pushState() 絕對不會觸發(fā) hashchange 事件,即使新的URL與舊的URL僅哈希不同也是不會觸發(fā)。
pushState()使用場景
history可實現(xiàn)無刷新修改URL或URL參數(shù)
window.history.replaceState('', '', `${window.location.origin}${window.location.pathname}type=a`);
replaceState() 方法
history.replaceState() 的使用與 history.pushState() 非常相似,區(qū)別在于 replaceState() 是修改了當(dāng)前的歷史記錄項而不是新建一個。 注意這并不會阻止其在全局瀏覽器歷史記錄中創(chuàng)建一個新的歷史記錄項。
使
popstate事件
使用 window.onpopstate來監(jiān)聽返回事件
window.onpopstate = funcRef;
funcRef : (Event:{state:any})=>void
每當(dāng)處于激活狀態(tài)的歷史記錄發(fā)生改變時,popstate事件就會被觸發(fā),在對應(yīng)的window的對象上觸發(fā)(window.onpopstate)。如果當(dāng)前處于激活狀態(tài)的歷史記錄條目是由history.pushState()方法創(chuàng)建,或者由history.replaceState()方法修改過的, 則popstate事件對象的state屬性包含了這個歷史記錄條目的state對象的一個拷貝.
**注意:
- 調(diào)用history.pushState()或者h(yuǎn)istory.replaceState()不會觸發(fā)popstate事件. popstate事件只會在瀏覽器某些行為下觸發(fā), 比如點擊后退、前進(jìn)按鈕(或者在JavaScript中調(diào)用history.back()、history.forward()、history.go()方法),此外,a 標(biāo)簽的錨點也會觸發(fā)該事件.
- 當(dāng)網(wǎng)頁加載時,各瀏覽器對popstate事件是否觸發(fā)有不同的表現(xiàn),Chrome 和 Safari會觸發(fā)popstate事件, 而Firefox不會.
pushState和replaceState如何監(jiān)聽呢?
我們可以自己通過訂閱-發(fā)布模式進(jìn)行實現(xiàn):首先使用Dep和Watch,訂閱和發(fā)布模式,其實是參考vue源碼 dep和wantcher之間實現(xiàn)方式的簡易版
class Dep { // 訂閱池 constructor(name){ this.id = new Date() //這里簡單的運用時間戳做訂閱池的ID this.subs = [] //該事件下被訂閱對象的集合 } defined(){ // 添加訂閱者 Dep.watch.add(this); } notify() { //通知訂閱者有變化 this.subs.forEach((e, i) => { if(typeof e.update === 'function'){ try { e.update.apply(e) //觸發(fā)訂閱者更新函數(shù) } catch(err){ console.warr(err) } } }) } } Dep.watch = null; class Watch { constructor(name, fn){ this.name = name; //訂閱消息的名稱 this.id = new Date(); //這里簡單的運用時間戳做訂閱者的ID this.callBack = fn; //訂閱消息發(fā)送改變時->訂閱者執(zhí)行的回調(diào)函數(shù) } add(dep) { //將訂閱者放入dep訂閱池 dep.subs.push(this); } update() { //將訂閱者更新方法 var cb = this.callBack; //賦值為了不改變函數(shù)內(nèi)調(diào)用的this cb(this.name); } }
重新history方法,并添加addHistoryListener方法
const addHistoryMethod= (function(){ var historyDep = new Dep()// 創(chuàng)建訂閱池 return function (name){ if(name==='historyChange'){ var event = new Watch(name,fn); Dep.watch = evnet; historyDep.defind();//增加訂閱者 Dep.watch = null; }else if(name==='pushState'||name==='replaceState'){ var method = history[name]; return function(){ method.apply(history,argumnets) historyDep.notify(); } } } })() window.addHistoryListener = addHistoryMethod('historyChange') history.pushState = addHistoryMethod('pushState'); history.replaceState = addHistoryMethod('replaceState');
封裝成功,測試使用例子
window.addHistoryListener('history',function(){ console.log('窗口history改變了') }) window.addHistoryListerer('history',function(){ console.log('窗口history改變,我也聽到了')// 可綁定多個監(jiān)聽事件 console.log(history.state) }) history.pushState({foo:bar}, 'title', '/car')
獲取當(dāng)前狀態(tài)
頁面加載時,或許會有個非null的狀態(tài)對象。這是有可能發(fā)生的,舉個例子,假如頁面(通過pushState() 或 replaceState() 方法)設(shè)置了狀態(tài)對象而后用戶重啟了瀏覽器。那么當(dāng)頁面重新加載時,頁面會接收一個onload事件,但沒有popstate 事件。然而,假如你讀取了history.state屬性,你將會得到如同popstate 被觸發(fā)時能得到的狀態(tài)對象。
你可以讀取當(dāng)前歷史記錄項的狀態(tài)對象state,而不必等待popstate 事件, 只需要這樣使用history.state屬性:
let currentState = history.state;
對比
pushState | replaceState |
---|---|
會在當(dāng)前頁面創(chuàng)建并激活新的歷史記錄,點擊返回按鈕會返回到之前的url | 會修改當(dāng)前的歷史記錄,按回退按鈕,會跳過被修改的url |
總結(jié)
history是實現(xiàn)無刷新路由的一種方式,特別適合我們進(jìn)行單頁面的開發(fā),保證系統(tǒng)穩(wěn)定的體驗性,但是history不監(jiān)聽pushState和replaceState的行為,只是監(jiān)聽go、back、forward行為。但我們可以使用訂閱發(fā)布模式,使用Dep和Watch,對pushState和replaceState事件進(jìn)行重新封裝,調(diào)用后會自動觸發(fā)notify方法,增加一個addHistoryListener的方法,用來增加監(jiān)聽回調(diào)(訂閱者)。
到此這篇關(guān)于詳解JavaScript實現(xiàn)監(jiān)聽路由變化的文章就介紹到這了,更多相關(guān)JavaScript監(jiān)聽路由變化內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JavaScript使用promise處理多重復(fù)請求
處理重復(fù)請求的文章想必大家也看過了很多,大多數(shù)都是分為在response返回之前發(fā)現(xiàn)重復(fù)請求就return掉的和使用節(jié)流/防抖來間接規(guī)避用戶頻繁操作兩種版本的。本文主要介紹了JavaScript使用promise處理多重復(fù)請求,感興趣的可以了解一下2021-05-05js css實現(xiàn)垂直方向自適應(yīng)的三角提示菜單
這篇文章主要為大家詳細(xì)介紹了js css實現(xiàn)垂直方向自適應(yīng)的三角提示菜單的相關(guān)資料,需要的朋友可以參考下2016-06-06用js查找法實現(xiàn)當(dāng)前欄目的高亮顯示的代碼
本文給大家介紹了使用js查找法實現(xiàn)當(dāng)前欄目的高亮顯示的代碼,非常不錯,具有一定的參考借鑒價值,需要的朋友參考下吧2007-11-11javascript 中關(guān)于array的常用方法詳解
這篇文章主要介紹了javascript 中關(guān)于array的常用方法的相關(guān)資料,需要的朋友可以參考下2017-05-05探討JavaScript標(biāo)簽位置的存放與功能有無關(guān)系
在網(wǎng)頁中,我們可以將JavaScript代碼放在html文件中任何位置,但一般放在head或body標(biāo)簽里面。一般來說,<script>元素放在哪里與其的功能作用是緊密相關(guān)的,通過本文我們一起學(xué)習(xí)下2016-01-01