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

来源:https://www.cnblogs.com/Vay123/archive/2019/12/27/12108154.html
-Advertisement-
Play Games

1. 需求 如果要你實現一個前端路由,應該如何實現瀏覽器的前進與後退 ? 2. 問題 首先瀏覽器中主要有這幾個限制,讓前端不能隨意的操作瀏覽器的瀏覽紀錄: •沒有提供監聽前進後退的事件。•不允許開發者讀取瀏覽紀錄,也就是 js 讀取不了瀏覽紀錄。•用戶可以手動輸入地址,或使用瀏覽器提供的前進後退來改 ...


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

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

 

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

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

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


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 這是一篇文字超多的博客,哈哈哈,廢話自行過濾··· 遇到問題 在開發中我們常會在ListView , RecycleView 列表中添加EditText輸入框,或者checkbox覆選框。 覆選框應該是用的比較多的,輸入框淘寶採用的是彈出框的方式,可能在一些特定的情況下,我們希望能夠直接在列表中輸入 ...
  • Janus說明 Android APP僅使用V1簽名,可能存在Janus漏洞(CVE 2017 13156),Janus漏洞(CVE 2017 13156)允許攻擊者在不改變原簽名的情況下任意修改APP中的代碼邏輯。 影響範圍:Android系統5.1.1 8.0 檢測方式 方式1 使用GetApk ...
  • MAC停靠欄 ~~~javascript ~~~ ...
  • 實例對象使用屬性和方法層層的搜索: 實例對象使用的屬性或者方法, 先在實例中查找, 找到了則直接使用; 找不到則, 再去實例對象的__proto__指向的原型對象prototype中找, 找到了則使用, 找不到則報錯。 <!DOCTYPE html> <html lang="en"> <head> ...
  • 原型的簡單的語法 構造函數,通過原型添加方法,以下語法,手動修改構造器的指向 實例化對象,並初始化,調用方法 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>title</title> <script> fu ...
  • 什麼樣子的數據是需要寫在原型中? 需要共用的數據就可以寫原型中 原型的作用之一: 數據共用 //屬性需要共用, 方法也需要共用 //不需要共用的數據寫在構造函數中,需要共用的數據寫在原型中 //構造函數 function Student(name,age,sex) { this.name=name; ...
  • 在ant design 的form組件中 能用於提交的組件比較少,所以我在這寫了一個可以單選、多選標簽提交的組件,調用非常簡單。 代碼: 1 import React,{Fragment} from 'react'; 2 import { Tag,Icon,Input } from 'antd'; ...
  • 本文涵蓋 面試題的引入 對事件迴圈面試題執行順序的一些疑問 通過面試題對微任務、事件迴圈、定時器等對深入理解 結論總結 面試題 面試題如下,大家可以先試著寫一下輸出結果,然後再看我下麵的詳細講解,看看會不會有什麼出入,如果把整個順序弄清楚 Node.js 的執行順序應該就沒問題了。 async fu ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...