摘要:執(zhí)行過程如下實(shí)現(xiàn)瀏覽器的前進(jìn)后退第二個(gè)方法就是用兩個(gè)棧實(shí)現(xiàn)瀏覽器的前進(jìn)后退功能。我們使用兩個(gè)棧,和,我們把首次瀏覽的頁(yè)面依次壓入棧,當(dāng)點(diǎn)擊后退按鈕時(shí),再依次從棧中出棧,并將出棧的數(shù)據(jù)依次放入棧。
如果要你實(shí)現(xiàn)一個(gè)前端路由,應(yīng)該如何實(shí)現(xiàn)瀏覽器的前進(jìn)與后退 ?
2. 問題首先瀏覽器中主要有這幾個(gè)限制,讓前端不能隨意的操作瀏覽器的瀏覽紀(jì)錄:
沒有提供監(jiān)聽前進(jìn)后退的事件。
不允許開發(fā)者讀取瀏覽紀(jì)錄,也就是 js 讀取不了瀏覽紀(jì)錄。
用戶可以手動(dòng)輸入地址,或使用瀏覽器提供的前進(jìn)后退來改變 url。
所以要實(shí)現(xiàn)一個(gè)自定義路由,解決方案是自己維護(hù)一份路由歷史的記錄,從而區(qū)分 前進(jìn)、刷新、回退。
下面介紹具體的方法。
3. 方法目前筆者知道的方法有兩種,一種是 在數(shù)組后面進(jìn)行增加與刪除,另外一種是 利用棧的后進(jìn)先出原理。
3.1 在數(shù)組最后進(jìn)行 增加與刪除通過監(jiān)聽路由的變化事件 hashchange,與路由的第一次加載事件 load ,判斷如下情況:
url 存在于瀏覽記錄中即為后退,后退時(shí),把當(dāng)前路由后面的瀏覽記錄刪除。
url 不存在于瀏覽記錄中即為前進(jìn),前進(jìn)時(shí),往數(shù)組里面 push 當(dāng)前的路由。
url 在瀏覽記錄的末端即為刷新,刷新時(shí),不對(duì)路由數(shù)組做任何操作。
另外,應(yīng)用的路由路徑中可能允許相同的路由出現(xiàn)多次(例如 A -> B -> A),所以給每個(gè)路由添加一個(gè) key 值來區(qū)分相同路由的不同實(shí)例。
注意:這個(gè)瀏覽記錄需要存儲(chǔ)在 sessionStorage 中,這樣用戶刷新后瀏覽記錄也可以恢復(fù)。
筆者之前實(shí)現(xiàn)的 用原生 js 實(shí)現(xiàn)的輕量級(jí)路由 ,就是用這種方法實(shí)現(xiàn)的,具體代碼如下:
// 路由構(gòu)造函數(shù) function Router() { this.routes = {}; //保存注冊(cè)的所有路由 this.routerViewId = "#routerView"; // 路由掛載點(diǎn) this.stackPages = true; // 多級(jí)頁(yè)面緩存 this.history = []; // 路由歷史 } Router.prototype = { init: function(config) { var self = this; //頁(yè)面首次加載 匹配路由 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) }, // 路由歷史紀(jì)錄變化 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, // 前進(jìn) index = 0, len = this.history.length; // 比較當(dāng)前路由的狀態(tài),得出是后退、前進(jìn)、刷新的狀態(tài)。 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) { // 后退,把歷史紀(jì)錄的最后一項(xiàng)刪除 this.historyFlag = "back" this.history.length = index + 1 } else if (refresh) { // 刷新,不做其他操作 this.historyFlag = "refresh" } else { // 前進(jìn),添加一條歷史紀(jì)錄 this.historyFlag = "forward" var item = { key: currentHash.query.key, hash: currentHash.path, query: currentHash.query } this.history.push(item) } // 如果不需要頁(yè)面緩存功能,每次都是刷新操作 if (!this.stackPages) { this.historyFlag = "forward" } window.sessionStorage[nameStr] = JSON.stringify(this.history) }, }
以上代碼只列出本次文章相關(guān)的內(nèi)容,完整的內(nèi)容請(qǐng)看 原生 js 實(shí)現(xiàn)的輕量級(jí)路由,且頁(yè)面跳轉(zhuǎn)間有緩存功能。
3.2 利用棧的 后進(jìn)者先出,先進(jìn)者后出 原理在說第二個(gè)方法之前,先來弄明白棧的定義與后進(jìn)者先出,先進(jìn)者后出原理。
3.2.1 定義棧的特點(diǎn):后進(jìn)者先出,先進(jìn)者后出。
舉一個(gè)生活中的例子說明:就是一摞疊在一起的盤子。我們平時(shí)放盤子的時(shí)候,都是從下往上一個(gè)一個(gè)放;取的時(shí)候,我們也是從上往下一個(gè)一個(gè)地依次取,不能從中間任意抽出。
因?yàn)闂5暮筮M(jìn)者先出,先進(jìn)者后出的特點(diǎn),所以只能棧一端進(jìn)行插入和刪除操作。這也和第一個(gè)方法的原理有異曲同工之妙。
下面用 JavaScript 來實(shí)現(xiàn)一個(gè)順序棧:
// 基于數(shù)組實(shí)現(xiàn)的順序棧 class ArrayStack { constructor(n) { this.items = []; // 數(shù)組 this.count = 0; // 棧中元素個(gè)數(shù) this.n = n; // 棧的大小 } // 入棧操作 push(item) { // 數(shù)組空間不夠了,直接返回 false,入棧失敗。 if (this.count === this.n) return false; // 將 item 放到下標(biāo)為 count 的位置,并且 count 加一 this.items[this.count] = item; ++this.count; return true; } // 出棧操作 pop() { // 棧為空,則直接返回 null if (this.count == 0) return null; // 返回下標(biāo)為 count-1 的數(shù)組元素,并且棧中元素個(gè)數(shù) count 減一 let tmp = items[this.count-1]; --this.count; return tmp; } }
其實(shí) JavaScript 中,數(shù)組是自動(dòng)擴(kuò)容的,并不需要指定數(shù)組的大小,也就是棧的大小 n 可以不指定的。
3.2.2 應(yīng)用棧的經(jīng)典應(yīng)用: 函數(shù)調(diào)用棧
操作系統(tǒng)給每個(gè)線程分配了一塊獨(dú)立的內(nèi)存空間,這塊內(nèi)存被組織成“?!边@種結(jié)構(gòu), 用來存儲(chǔ)函數(shù)調(diào)用時(shí)的臨時(shí)變量。每進(jìn)入一個(gè)函數(shù),就會(huì)將臨時(shí)變量作為一個(gè)棧幀入棧,當(dāng)被調(diào)用函數(shù)執(zhí)行完成,返回之后,將這個(gè)函數(shù)對(duì)應(yīng)的棧幀出棧。為了讓你更好地理解,我們一塊來看下這段代碼的執(zhí)行過程。
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();
上面代碼也很簡(jiǎn)單,就是執(zhí)行 main 函數(shù)求和,main 函數(shù)里面又調(diào)用了 add 函數(shù),先調(diào)用的先進(jìn)入棧。
執(zhí)行過程如下:
3.2.3 實(shí)現(xiàn)瀏覽器的前進(jìn)、后退第二個(gè)方法就是:用兩個(gè)棧實(shí)現(xiàn)瀏覽器的前進(jìn)、后退功能。
我們使用兩個(gè)棧,X 和 Y,我們把首次瀏覽的頁(yè)面依次壓入棧 X,當(dāng)點(diǎn)擊后退按鈕時(shí),再依次從棧 X 中出棧,并將出棧的數(shù)據(jù)依次放入棧 Y。當(dāng)我們點(diǎn)擊前進(jìn)按鈕時(shí),我們依次從棧 Y 中取出數(shù)據(jù),放入棧 X 中。當(dāng)棧 X 中沒有數(shù)據(jù)時(shí),那就說明沒有頁(yè)面可以繼續(xù)后退瀏覽了。當(dāng)棧 Y 中沒有數(shù)據(jù),那就說明沒有頁(yè)面可以點(diǎn)擊前進(jìn)按鈕瀏覽了。
比如你順序查看了 a,b,c 三個(gè)頁(yè)面,我們就依次把 a,b,c 壓入棧,這個(gè)時(shí)候,兩個(gè)棧的數(shù)據(jù)如下:
當(dāng)你通過瀏覽器的后退按鈕,從頁(yè)面 c 后退到頁(yè)面 a 之后,我們就依次把 c 和 b 從棧 X 中彈出,并且依次放入到棧 Y。這個(gè)時(shí)候,兩個(gè)棧的數(shù)據(jù)就是這個(gè)樣子:
這個(gè)時(shí)候你又想看頁(yè)面 b,于是你又點(diǎn)擊前進(jìn)按鈕回到 b 頁(yè)面,我們就把 b 再?gòu)臈?Y 中出棧,放入棧 X 中。此時(shí)兩個(gè)棧的數(shù)據(jù)是這個(gè)樣子:
這個(gè)時(shí)候,你通過頁(yè)面 b 又跳轉(zhuǎn)到新的頁(yè)面 d 了,頁(yè)面 c 就無法再通過前進(jìn)、后退按鈕重復(fù)查看了,所以需要清空棧 Y。此時(shí)兩個(gè)棧的數(shù)據(jù)這個(gè)樣子:
如果用代碼來實(shí)現(xiàn),會(huì)是怎樣的呢 ?各位可以想一下。
其實(shí)就是在第一個(gè)方法的代碼里面, 添加多一份路由歷史紀(jì)錄的數(shù)組即可,對(duì)這兩份歷史紀(jì)錄的操作如上面示例圖所示即可,也就是對(duì)數(shù)組的增加和刪除操作而已, 這里就不展開了。
其中第二個(gè)方法與參考了 王爭(zhēng)老師的 數(shù)據(jù)結(jié)構(gòu)與算法之美。
4. 最后博客首更地址 :https://github.com/biaochenxuying/blog
往期精文
十分鐘弄懂:數(shù)據(jù)結(jié)構(gòu)與算法之美 - 時(shí)間和空間復(fù)雜度
一張思維導(dǎo)圖輔助你深入了解 Vue | Vue-Router | Vuex 源碼架構(gòu)
Vue + TypeScript + Element 項(xiàng)目實(shí)戰(zhàn)及踩坑記
vue-cli3.x 新特性及踩坑記
那些必會(huì)用到的 ES6 精粹
參考文章:數(shù)據(jù)結(jié)構(gòu)與算法之美
歡迎關(guān)注以下公眾號(hào) 全棧修煉,學(xué)到不一樣的武功秘籍 !
關(guān)注公眾號(hào)并回復(fù) 福利 可領(lǐng)取免費(fèi)學(xué)習(xí)資料,福利詳情請(qǐng)猛戳: 免費(fèi)資源獲取--Python、Java、Linux、Go、node、vue、react、javaScript
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/104536.html
摘要:部分比較重要部分比較重要重點(diǎn)是下面的,這里操作我們借助實(shí)現(xiàn)的邏輯不難,但是利用總是沒有所謂前端路由的那種味,必然也不是主流方法。而現(xiàn)在的前端路由也是基于這個(gè)原理實(shí)現(xiàn)的。和異曲同工,而且前者可以在該事件之外任何地方隨時(shí)使用。 SPA 前端路由原理與實(shí)現(xiàn)方式 通常 SPA 中前端路由有2中實(shí)現(xiàn)方式,本文會(huì)簡(jiǎn)單快速總結(jié)這兩種方法及其實(shí)現(xiàn): 修改 url 中 Hash 利用 H5 中的 hi...
摘要:部分比較重要部分比較重要重點(diǎn)是下面的,這里操作我們借助實(shí)現(xiàn)的邏輯不難,但是利用總是沒有所謂前端路由的那種味,必然也不是主流方法。而現(xiàn)在的前端路由也是基于這個(gè)原理實(shí)現(xiàn)的。和異曲同工,而且前者可以在該事件之外任何地方隨時(shí)使用。 SPA 前端路由原理與實(shí)現(xiàn)方式 通常 SPA 中前端路由有2中實(shí)現(xiàn)方式,本文會(huì)簡(jiǎn)單快速總結(jié)這兩種方法及其實(shí)現(xiàn): 修改 url 中 Hash 利用 H5 中的 hi...
摘要:出于安全的考慮,開發(fā)人員無法得知用戶瀏覽過的。這個(gè)方法接受一個(gè)參數(shù),表示向后或向前跳轉(zhuǎn)的頁(yè)面數(shù)的一個(gè)整數(shù)值。該事件觸發(fā)時(shí),該對(duì)象會(huì)傳入回調(diào)函數(shù)。假定當(dāng)前網(wǎng)頁(yè)是。顯示為顯示為顯示為顯示為顯示為顯示為三事件事件是對(duì)象上的事件,配合和方法使用。 首先要學(xué)習(xí)一下history對(duì)象,history對(duì)象保存著用戶的上網(wǎng)記錄,從瀏覽器窗口打開的那一刻算起。出于安全的考慮,開發(fā)人員無法得知用戶瀏覽過的...
摘要:由于這種通信方式不需要頁(yè)面的刷新動(dòng)作,因而無論與后臺(tái)發(fā)生了多少次通信,瀏覽器的會(huì)一直保持在初始地址不變。前端路由的值通常應(yīng)用在單頁(yè)面應(yīng)用中。 后端路由 * path(路由分發(fā)) 針對(duì)不同的路由對(duì)應(yīng)不同的回調(diào)函數(shù)處理(req, res, next) * req;獲取請(qǐng)求參數(shù) * res:返回請(qǐng)求數(shù)據(jù) * next: 調(diào)用后續(xù)的回調(diào)函數(shù) 前端路由 * 路由是...
摘要:最新一直在看關(guān)于和路由這塊的知識(shí),最終發(fā)現(xiàn)這些路由框架的模塊功能的實(shí)現(xiàn)都是基于瀏覽器原生路由的。在瀏覽器中實(shí)現(xiàn)前端路由主要有兩種方式一個(gè)是我們常用的,另一個(gè)是提供的。該對(duì)象的和分別表示的各個(gè)部分,它們因此被稱為分解屬性。 最新一直在看關(guān)于 Vue 和 React 路由這塊的知識(shí),最終發(fā)現(xiàn)這些路由框架的模塊功能的實(shí)現(xiàn)都是基于瀏覽器原生路由 API?的。本著追根溯源的初心,于是就想著將瀏覽...
閱讀 895·2021-09-22 15:17
閱讀 1918·2021-09-22 15:06
閱讀 2219·2021-09-08 09:35
閱讀 5105·2021-09-01 11:43
閱讀 3480·2019-08-30 15:55
閱讀 2154·2019-08-30 12:48
閱讀 3155·2019-08-30 12:45
閱讀 1784·2019-08-29 17:31