一、前端路由有兩種實現方式
- hash模式的實現原理
1、早期的前端路由的實現就是基於 window.location.hash 來實現的,其實現的原理十分簡單,window.location.hash 的值就是 URL 中的 # 后面的值,例如:http://localhost:3000/#/orange 這個路由的 window.location.hash 為 #/orange;
-
- hash路由
/** * hash路由:通過監聽url中的hash變化來進行路由跳轉 * 當hash發生變化時,瀏覽器更新視圖並不會重新發起請求, * 而是會觸發 hashchange 事件 * 優點:瀏覽器兼容性較好,連 IE8 都支持 * 缺點:路徑在井號 # 的后面,比較丑,問題在於url中一直存在#不夠美觀 * 而且hash路由更像是Hack而非標准,相信隨着發展更加標准化的History API * 會逐步蠶食掉hash路由的市場 */
- 實現主要基於以下幾個方面的特性
1、URL 中的 hash 值只是客戶端的一種狀態,也就是說當向服務器發出請求時,hash 部分不會被發送;
2、hash 值的改變,都會在瀏覽器的訪問歷史中增加一個記錄,因此我們能通過瀏覽器的回退,前進按鈕控制 hash 的切換;
3、可以通過設置a標簽,並通過設置 href 屬性,例如href = ‘#/blue’,當點擊標簽的時候,url的 hash 值會發生改變,在當前url的后面增加上’#/blue’,
同時觸發hashchange,再回調函數中進行處理; 4、前進后退的時候,可以直接通過js來對 location.hash 進行賦值,改變url的 hash 值,例如 location.hash = ‘#/blue’即可,此時url會改變,
也會觸發hashchange事件。
5、因此我們可以使用 hashchange 事件來監聽 hash 值得變化,從而對頁面進行跳轉(渲染); - 實現方案:
-
html代碼:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>前端路由</title> </head> <body> <h1>hello , Hash router!!!</h1> <div style="margin-bottom:100px"> <ul> <li><a href="#/">turn white</a></li> <li><a href="#/blue">turn blue</a></li> <li><a href="#/green">turn green</a> <li><a href="#/red">turn red</a></li> <li><a href="#/orange">turn orange</a> </ul> <button id="btn">回退</button> <div id="app" style="margin-top:50px;height:100px"> </div> </div> <script> function render() { app.innerHTML = '渲染容器'+window.location.hash; app.style.backgroundColor="pink" } window.addEventListener('hashchange', render) </script> <script src="./js/router.js"></script> </body> </html>
- js代碼
class Router { constructor() { /** * 以鍵值對的形式存儲路由 */ this.routers = new Object(); /** * 當前路由的URL */ this.currentUrl = ""; /** * 記錄出現過的hash */ this.history = []; /** * 作為指針,默認指向this.history的末尾,根據后退前進指向history中不同的hash */ this.currentIndex = this.history.length - 1; /** * 默認不是后退操作 */ this.isBack = false; } /** * 都定義在原型上,后面的覆蓋前面的,這個不執行 */ route(path, callback) { console.log(1); } } /** * 將路由的hash以及對應的callback函數儲存 * @param {*} path * @param {*} callback */ Router.prototype.route = function (routes) { for (let route of routes) { this.routers[route.path] = route.callback || function () {}; } }; /** * 當頁面刷新的時候 */ Router.prototype.refresh = function () { /** * 獲取當前頁面中的hash路徑 */ this.currentUrl = window.location.hash.slice("1") || "/"; /** * 不是后退才執行 */ if (!this.isBack) { if (this.currentIndex < this.history.length - 1) this.history = this.history.slice(0, this.currentIndex + 1); /** * 將當前hash路由推入數組儲存,指針向前移動 */ this.history.push(this.currentUrl); this.currentIndex++; } this.isBack = false; /** * 執行當前hash路徑的回調函數 */ this.routers[this.currentUrl](); console.log("refresh"); console.log(this.history); console.log(this.currentIndex); }; /** * 當頁面后退,回退的過程中會觸發hashchange,將hash重新放入,索引增加 */ Router.prototype.back = function () { console.log("back"); console.log(this.history); console.log(this.currentIndex); // 后退操作設置為true this.isBack = true; /** * 如果指針小於0的話就不存在對應hash路由了,因此鎖定指針為0即可 */ this.currentIndex <= 0 ? (this.currentIndex = 0) : (this.currentIndex = this.currentIndex - 1); /** * 隨着后退,location.hash也應該隨之變化 * 並執行指針目前指向hash路由對應的callback */ location.hash = `#${this.history[this.currentIndex]}`; this.routers[this.history[this.currentIndex]](); }; /** * 初始化,監聽頁面的加載與hash只的變化 */ Router.prototype.init = function () { /** * 修改this指向,否則指向window */ window.addEventListener("load", this.refresh.bind(this), false); window.addEventListener("hashchange", this.refresh.bind(this), false); }; const route = new Router(); /** * 初始化 */ route.init(); const routes = [ { path: "/", callback: function () { let el = document.body; el.style.backgroundColor = "#fff"; }, }, { path: "/blue", callback: function () { let el = document.body; el.style.backgroundColor = "blue"; }, }, { path: "/green", callback: function () { let el = document.body; el.style.backgroundColor = "green"; }, }, { path: "/red", callback: function () { let el = document.body; el.style.backgroundColor = "red"; }, }, { path: "/orange", callback: function () { let el = document.body; el.style.backgroundColor = "orange"; }, }, ]; /** * 將hash值與cb綁定 */ route.route(routes); window.onload = function () { let btn = document.getElementById("btn"); btn.addEventListener("click", route.back.bind(route), false); };
-
- hash路由
- history模式的實現原理
HTML5 提供了 History API 來實現 URL 的變化,其中最主要的 API 有以下兩個:
1、history.pushState() 新增一個歷史記錄;
2、history.replaceState() 直接替換當前歷史記錄;
相同點:可以在不進行刷新的情況下,操作瀏覽器的歷史記錄
-
- history.pushState / replaceState 方法接受三個參數,依次為:
state:一個與指定網址相關的狀態對象,popstate事件觸發時,該對象會傳入回調函數。如果不需要這個對象,此處可以填null。 title:新頁面的標題,但是所有瀏覽器目前都忽略這個值,因此這里可以填null。 url:新的網址,必須與當前頁面處在同一個域。瀏覽器的地址欄將顯示這個網址。
eg:window.history.pushState(null, null, path);window.history.replaceState(null, null, path); - history路由模式的實現主要基於以下幾個方面的特性
1、pushState / replaceState 兩個 API 來操作實現 URL 的變化;
2、我們可以使用 popstate 事件來監聽 URL 的變化,從而對頁面進行跳轉(渲染);
3、pushState / replaceState 或 <a> 標簽 並不會觸發 popstate 事件,只有用戶點擊瀏覽器倒退按鈕和前進按鈕,或者使用 JavaScript 調用back
、forward
、go
方法時才會觸發,好在我們可以攔截 pushState/replaceState的調用和<a>
標簽的點擊事件來檢測 URL 變化 所以我們需要手動觸發頁面跳轉(渲染); - history模式的問題
1、history 路由模式雖然好看,但是這種模式要玩兒好,還需要后台配置支持,因為我們的應用是個單頁的客戶端應用,如果后台沒有正確的配置,當用戶在瀏覽器直接訪問一些沒有
配置的路徑就會返回404,但因為沒有 # 號,所以當用戶刷新頁面之類的操作時,瀏覽器還是會給服務器發送請求。為了避免出現這種情況,所以這個實現需要服務器的支持,
需要把所有路由都重定向到根頁面;
2、所以呢,你要在服務端增加一個覆蓋所有情況的候選資源,如果 URL 匹配不到任何靜態資源,應該返回同一個 index.html 頁面,這個頁面就是你app依賴的頁面; - 參考鏈接:https://router.vuejs.org/zh/guide/essentials/history-mode.html
- 實現方案:
- html代碼:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>前端路由</title> </head> <body> <h1>hello , History router!!!</h1> <ul> <li><a href="/">turn white</a></li> <li><a href="http://localhost:3000/color/blue">turn blue</a></li> <li><a href="http://localhost:3000/color/green">turn green</a> <li><a href="http://localhost:3000/color/red">turn red</a></li> <li><a href="http://localhost:3000/color/orange">turn orange</a> </ul> <script src="./js/history.js"></script> </body> </html>
- js代碼
/** * history路由 */ class Router { constructor() { /** * 以鍵值對的形式存儲路由 */ this.routers = new Object(); } } /** * 監聽頁面的popstate事件 */ Router.prototype.bindPopState = function (e) { const path = e.state && e.state.path; this.routers[path] && this.routers[path](); }; /** * 將路由的path以及對應的callback函數儲存 * @param {*} path * @param {*} callback */ Router.prototype.route = function (routes) { for (let route of routes) { this.routers[route.path] = route.callback || function () {}; } }; /** * 初始化,直接替換當前歷史紀錄,並用狀態對象進行存儲 */ Router.prototype.init = function (path) { window.history.replaceState({ path: path }, null, path); this.routers[path] && this.routers[path](); /** * 加入事件監聽 */ window.addEventListener("popstate", this.bindPopState.bind(this), false); }; /** * 更新頁面,新增一個歷史紀錄 */ Router.prototype.go = function (path) { window.history.pushState({ path: path }, null, path); this.routers[path] && this.routers[path](); }; const route = new Router(); route.init(window.location.href); const routes = [ { path: "http://localhost:3000/", callback: function () { let el = document.body; el.style.backgroundColor = "#fff"; }, }, { path: "http://localhost:3000/color/blue", callback: function () { let el = document.body; el.style.backgroundColor = "blue"; }, }, { path: "http://localhost:3000/color/green", callback: function () { let el = document.body; el.style.backgroundColor = "green"; }, }, { path: "http://localhost:3000/color/red", callback: function () { let el = document.body; el.style.backgroundColor = "red"; }, }, { path: "http://localhost:3000/color/orange", callback: function () { let el = document.body; el.style.backgroundColor = "orange"; }, }, ]; /** * 將hash值與cb綁定 */ route.route(routes); /** * a標簽會跳轉頁面,阻止 */ window.addEventListener( "click", function (e) { var e = e || window.event; var target = e.target || e.srcElement; if ((target.tagName = "A")) { e.preventDefault(); route.go(e.target.getAttribute("href")); } }, false );
- html代碼:
- history.pushState / replaceState 方法接受三個參數,依次為:
二、vue-router出現的問題
- vue-router的核心是,通過 vue.use 注冊插件,在插件的 install 方法中獲取用戶配置的 router 對象,當瀏覽器的 URL 發生變化的時候,根據 router 對象匹配相應的路由,獲取組件,並將組件渲染到視圖上;
- 如何渲染 router-view 組件?
- 如何在 install 方法中獲取vue實例上的router屬性???