JavaScript實作監聽路由變化
前端實作路由變化主要有兩種方式,這兩種方式最大特點就是實作URL切換無重繪功能
- 通過hash改變,利用window.onhashchange 監聽,
- 通過history的改變,進行js操作加載頁面,然而history并不像hash那樣簡單,因為history的改變,除了瀏覽器的幾個前進后退(使用 history.back(), history.forward()和 history.go() 方法來完成在用戶歷史記錄中向后和向前的跳轉,)等操作會主動觸發popstate 事件,pushState,replaceState 并不會觸發popstate事件,
history
主要來了解一下History
pushState()方法
需要三個引數: 一個狀態物件, 一個標題 (目前被忽略), 和 (可選的) 一個URL. 讓我們來解釋下這三個引數詳細內容:
狀態物件(state object) — 狀態物件state是一個JavaScript物件,通過pushState () 創建新的歷史記錄條目,無論什么時候用戶導航到新的狀態,popstate事件就會被觸發,且該事件的state屬性包含該歷史記錄條目狀態物件的副本, 狀態物件可以是能被序列化的任何東西,原因在于Firefox將狀態物件保存在用戶的磁盤上,以便在用戶重啟瀏覽器時使用,我們規定了狀態物件在序列化表示后有640k的大小限制,如果你給 pushState() 方法傳了一個序列化后大于640k的狀態物件,該方法會拋出例外,如果你需要更大的空間,建議使用 sessionStorage 以及 localStorage.
標題(title) — Firefox 目前忽略這個引數,但未來可能會用到,在此處傳一個空字串應該可以安全的防范未來這個方法的更改,或者,你可以為跳轉的state傳遞一個短標題,
URL — 該引數定義了新的歷史URL記錄,注意,呼叫pushState() 后瀏覽器并不會立即加載這個URL,但可能會在稍后某些情況下加載這個URL(不會加載該資源,但我們可以通過監聽它的改變,來改變視圖,這就成為單頁面的路由實作的一個方式,實作頁面無重繪),比如在用戶重新打開瀏覽器時,新URL不必須為絕對路徑,如果新URL是相對路徑,那么它將被作為相對于當前URL處理,新URL必須與當前URL同源(同源策略),否則 pushState()會拋出一個例外,該引數是可選的,預設為當前URL,
在某種意義上,呼叫 pushState() 與 設定 window.location = “#foo” 類似,二者都會在當前頁面創建并激活新的歷史記錄,但 pushState()具有如下幾條優點:
新的 URL可以是與當前URL同源的任意URL ,相反,只有在修改哈希時,設定 window.location 才能是同一個 document,
如果你不想改URL,就不用改,相反,設定 window.location = “#foo”;在當前哈希不是 #foo 時, 才能創建新的歷史記錄項,
你可以將任意資料和新的歷史記錄項相關聯,而基于哈希的方式,要把所有相關資料編碼為短字串,
如果 標題 隨后還會被瀏覽器所用到,那么這個資料是可以被使用的(哈希則不是),
注意pushState() 絕對不會觸發 hashchange 事件,即使新的URL與舊的URL僅哈希不同也是不會觸發,
pushState()使用場景
- history可實作無重繪修改URL或URL引數
window.history.replaceState('', '', `${window.location.origin}${window.location.pathname}type=a`);
replaceState() 方法
history.replaceState() 的使用與 history.pushState() 非常相似,區別在于 replaceState() 是修改了當前的歷史記錄項而不是新建一個, 注意這并不會阻止其在全域瀏覽器歷史記錄中創建一個新的歷史記錄項,
使
popstate事件
使用 window.onpopstate來監聽回傳事件
window.onpopstate = funcRef;
funcRef : (Event:{state:any})=>void
每當處于激活狀態的歷史記錄發生改變時,popstate事件就會被觸發,在對應的window的物件上觸發(window.onpopstate),如果當前處于激活狀態的歷史記錄條目是由history.pushState()方法創建,或者由history.replaceState()方法修改過的, 則popstate事件物件的state屬性包含了這個歷史記錄條目的state物件的一個拷貝.
**注意:
-
呼叫
history.pushState()或者history.replaceState()不會觸發popstate事件.popstate事件只會在瀏覽器某些行為下觸發, 比如點擊后退、前進按鈕(或者在JavaScript中呼叫history.back()、history.forward()、history.go()方法),此外,a 標簽的錨點也會觸發該事件. -
當網頁加載時,各瀏覽器對
popstate事件是否觸發有不同的表現,Chrome 和 Safari會觸發popstate事件, 而Firefox不會.
pushState和replaceState如何監聽呢?
我們可以自己通過訂閱-發布模式進行實作:首先使用Dep和Watch,訂閱和發布模式,其實是參考vue原始碼 dep和wantcher之間實作方式的簡易版
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) //觸發訂閱者更新函式
} catch(err){
console.warr(err)
}
}
})
}
}
Dep.watch = null;
class Watch {
constructor(name, fn){
this.name = name; //訂閱訊息的名稱
this.id = new Date(); //這里簡單的運用時間戳做訂閱者的ID
this.callBack = fn; //訂閱訊息發送改變時->訂閱者執行的回呼函式
}
add(dep) { //將訂閱者放入dep訂閱池
dep.subs.push(this);
}
update() { //將訂閱者更新方法
var cb = this.callBack; //賦值為了不改變函式內呼叫的this
cb(this.name);
}
}
重新history方法,并添加addHistoryListener方法
const addHistoryMethod= (function(){
var historyDep = new Dep()// 創建訂閱池
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改變,我也聽到了')// 可系結多個監聽事件
console.log(history.state)
})
history.pushState({foo:bar}, 'title', '/car')
獲取當前狀態
頁面加載時,或許會有個非null的狀態物件,這是有可能發生的,舉個例子,假如頁面(通過pushState() 或 replaceState() 方法)設定了狀態物件而后用戶重啟了瀏覽器,那么當頁面重新加載時,頁面會接收一個onload事件,但沒有popstate 事件,然而,假如你讀取了history.state屬性,你將會得到如同popstate 被觸發時能得到的狀態物件,
你可以讀取當前歷史記錄項的狀態物件state,而不必等待popstate 事件, 只需要這樣使用history.state屬性:
let currentState = history.state;
對比
pushState | replaceState |
|---|---|
| 會在當前頁面創建并激活新的歷史記錄,點擊回傳按鈕會回傳到之前的url | 會修改當前的歷史記錄,按回退按鈕,會跳過被修改的url |
總結
history是實作無重繪路由的一種方式,特別適合我們進行單頁面的開發,保證系統穩定的體驗性,但是history不監聽pushState和replaceState的行為,只是監聽go、back、forward行為,但我們可以使用訂閱發布模式,使用Dep和Watch,對pushState和replaceState事件進行重新封裝,呼叫后會自動觸發notify方法,增加一個addHistoryListener的方法,用來增加監聽回呼(訂閱者),
轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/293991.html
標籤:其他
上一篇:0基礎學HTML-css前端-1
