1. 需求
如果要你實作一個前端路由,應該如何實作瀏覽器的前進與后退 ?
2. 問題
首先瀏覽器中主要有這幾個限制,讓前端不能隨意的操作瀏覽器的瀏覽紀錄:
?沒有提供監聽前進后退的事件,?不允許開發者讀取瀏覽紀錄,也就是 js 讀取不了瀏覽紀錄,?用戶可以手動輸入地址,或使用瀏覽器提供的前進后退來改變 url,
所以要實作一個自定義路由,解決方案是自己維護一份路由歷史的記錄,從而區分 前進、重繪、回退,
下面介紹具體的方法,
3. 方法
目前筆者知道的方法有兩種,一種是 在陣列后面進行增加與洗掉,另外一種是 利用堆疊的后進先出原理,
我自己是一名從事了多年開發的web前端老程式員,目前辭職在做自己的web前端私人定制課程,今年年初我花了一個月整理了一份最適合2019年學習的web前端學習干貨,各種框架都有整理,送給每一位前端小伙伴,想要獲取的可以關注我并添加我的web前端交流裙【六零零】+【六一零】+【一五一】,即可免費獲取,
3.1 在陣列最后進行 增加與洗掉
通過監聽路由的變化事件 hashchange,與路由的第一次加載事件 load ,判斷如下情況:
?url 存在于瀏覽記錄中即為后退,后退時,把當前路由后面的瀏覽記錄洗掉,?url 不存在于瀏覽記錄中即為前進,前進時,往陣列里面 push 當前的路由,?url 在瀏覽記錄的末端即為重繪,重繪時,不對路由陣列做任何操作,
另外,應用的路由路徑中可能允許相同的路由出現多次(例如 A -> B -> A),所以給每個路由添加一個 key 值來區分相同路由的不同實體,
注意:這個瀏覽記錄需要存盤在 sessionStorage 中,這樣用戶重繪后瀏覽記錄也可以恢復,
筆者之前實作的 用原生 js 實作的輕量級路由 ,就是用這種方法實作的,具體代碼如下:
// 路由建構式function Router() { this.routes = {}; //保存注冊的所有路由 this.routerViewId = "#routerView"; // 路由掛載點 this.stackPages = true; // 多級頁面快取 this.history = []; // 路由歷史} Router.prototype = { init: function(config) { var self = this; //頁面首次加載 匹配路由 window.addEventListener('load', function(event) { // console.log('load', event); self.historyChange(event) }, false) //路由切換 window.addEventListener('hashchange', function(event) { // console.log('hashchange', event); self.historyChange(event) }, false) }, // 路由歷史紀錄變化 historyChange: function(event) { var currentHash = util.getParamsUrl(); var nameStr = "router-history" this.history = window.sessionStorage[nameStr] ? JSON.parse(window.sessionStorage[nameStr]) : [] var back = false, // 后退 refresh = false, // 重繪 forward = false, // 前進 index = 0, len = this.history.length; // 比較當前路由的狀態,得出是后退、前進、重繪的狀態, for (var i = 0; i < len; i++) { var h = this.history[i]; if (h.hash === currentHash.path && h.key === currentHash.query.key) { index = i if (i === len - 1) { refresh = true } else { back = true } break; } else { forward = true } } if (back) { // 后退,把歷史紀錄的最后一項洗掉 this.historyFlag = 'back' this.history.length = index + 1 } else if (refresh) { // 重繪,不做其他操作 this.historyFlag = 'refresh' } else { // 前進,添加一條歷史紀錄 this.historyFlag = 'forward' var item = { key: currentHash.query.key, hash: currentHash.path, query: currentHash.query } this.history.push(item) } // 如果不需要頁面快取功能,每次都是重繪操作 if (!this.stackPages) { this.historyFlag = 'forward' } window.sessionStorage[nameStr] = JSON.stringify(this.history) }, }
3.2 利用堆疊的 后進者先出,先進者后出 原理
在說第二個方法之前,先來弄明白堆疊的定義與后進者先出,先進者后出原理,
3.2.1 定義
堆疊的特點:后進者先出,先進者后出,
舉一個生活中的例子說明:就是一摞疊在一起的盤子,我們平時放盤子的時候,都是從下往上一個一個放;取的時候,我們也是從上往下一個一個地依次取,不能從中間任意抽出,
因為堆疊的后進者先出,先進者后出的特點,所以只能堆疊一端進行插入和洗掉操作,這也和第一個方法的原理有異曲同工之妙,
下面用 JavaScript 來實作一個順序堆疊:
// 基于陣列實作的順序堆疊class ArrayStack { constructor(n) { this.items = []; // 陣列 this.count = 0; // 堆疊中元素個數 this.n = n; // 堆疊的大小 } // 入堆疊操作 push(item) { // 陣列空間不夠了,直接回傳 false,入堆疊失敗, if (this.count === this.n) return false; // 將 item 放到下標為 count 的位置,并且 count 加一 this.items[this.count] = item; ++this.count; return true; } // 出堆疊操作 pop() { // 堆疊為空,則直接回傳 null if (this.count == 0) return null; // 回傳下標為 count-1 的陣列元素,并且堆疊中元素個數 count 減一 let tmp = items[this.count-1]; --this.count; return tmp; }}
其實 JavaScript 中,陣列是自動擴容的,并不需要指定陣列的大小,也就是堆疊的大小 n 可以不指定的,
3.2.2 應用
堆疊的經典應用: 函式呼叫堆疊
作業系統給每個執行緒分配了一塊獨立的記憶體空間,這塊記憶體被組織成“堆疊”這種結構, 用來存盤函式呼叫時的臨時變數,每進入一個函式,就會將臨時變數作為一個堆疊幀入堆疊,當被呼叫函式執行完成,回傳之后,將這個函式對應的堆疊幀出堆疊,為了讓你更好地理解,我們一塊來看下這段代碼的執行程序,
function add(x, y) { let sum = 0; sum = x + y; return sum;} function main() { let a = 1; let ret = 0; let res = 0; ret = add(3, 5); res = a + ret; console.log("res: ", res); reuturn 0;}
上面代碼也很簡單,就是執行 main 函式求和,main 函式里面又呼叫了 add 函式,先呼叫的先進入堆疊,
執行程序如下:
3.2.3 實作瀏覽器的前進、后退
第二個方法就是:用兩個堆疊實作瀏覽器的前進、后退功能,
我們使用兩個堆疊,X 和 Y,我們把首次瀏覽的頁面依次壓入堆疊 X,當點擊后退按鈕時,再依次從堆疊 X 中出堆疊,并將出堆疊的資料依次放入堆疊 Y,當我們點擊前進按鈕時,我們依次從堆疊 Y 中取出資料,放入堆疊 X 中,當堆疊 X 中沒有資料時,那就說明沒有頁面可以繼續后退瀏覽了,當堆疊 Y 中沒有資料,那就說明沒有頁面可以點擊前進按鈕瀏覽了,
比如你順序查看了 a,b,c 三個頁面,我們就依次把 a,b,c 壓入堆疊,這個時候,兩個堆疊的資料如下:
當你通過瀏覽器的后退按鈕,從頁面 c 后退到頁面 a 之后,我們就依次把 c 和 b 從堆疊 X 中彈出,并且依次放入到堆疊 Y,這個時候,兩個堆疊的資料就是這個樣子:
這個時候你又想看頁面 b,于是你又點擊前進按鈕回到 b 頁面,我們就把 b 再從堆疊 Y 中出堆疊,放入堆疊 X 中,此時兩個堆疊的資料是這個樣子:
這個時候,你通過頁面 b 又跳轉到新的頁面 d 了,頁面 c 就無法再通過前進、后退按鈕重復查看了,所以需要清空堆疊 Y,此時兩個堆疊的資料這個樣子:
如果用代碼來實作,會是怎樣的呢 ?各位可以想一下,
其實就是在第一個方法的代碼里面, 添加多一份路由歷史紀錄的陣列即可,對這兩份歷史紀錄的操作如上面示例圖所示即可,也就是對陣列的增加和洗掉操作而已, 這里就不展開了,
其中第二個方法與參考了 王爭老師的 資料結構與演算法之美,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/159139.html
標籤:JavaScript
上一篇:CORS
下一篇:Vue-cli 多頁相關配置記錄
