實現一個前端路由,如何實現瀏覽器的前進與后退?


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。此時兩個棧的數據這個樣子:

實現一個前端路由,如何實現瀏覽器的前進與后退?

 

如果用代碼來實現,會是怎樣的呢 ?各位可以想一下。

其實就是在第一個方法的代碼里面, 添加多一份路由歷史紀錄的數組即可,對這兩份歷史紀錄的操作如上面示例圖所示即可,也就是對數組的增加和刪除操作而已, 這里就不展開了。

其中第二個方法與參考了 王爭老師的 數據結構與算法之美。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM