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

