SPA就是單頁面應用,即single page application,經過看代碼就能夠發現,整個網站就只有一個Html文件。html
github地址vue
目前來講,不管是vue,仍是react,spa的路由實現方式無非就是如下二者:html5
hash用的仍是比較多的,可是這種方式的url會比較醜陋,會出現 #; 而history api就能夠很好的解決這個問題,可是須要後端配置,而且因爲是html5中的api,因此兼容性還不是很好,用的時候須要肯定你的用戶,再作決定。node
本身動手,豐衣足食! 咱們本身來寫一個router,也許對齊理解就會更加明白了。通常,咱們在使用vue和react的router的時候,不難發現他們都是構造函數,而後生成一個實例,即面向對象的方式。react
這裏固然也須要使用面向對象的方式來實現,這種方式的可擴展性、可維護性都會比較好一些。git
github地址github
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>router</title> <style> html, body { width: 100%; height: 100%; margin: 0; } div.router-wrap { width: 100%; height: 100%; background: #fefefe; } a { padding: 10px; color: pink; font-size: 25px; font-weight: bold; text-decoration: none; } </style> </head> <body> <div class="router-wrap"> <a href="#/black">黑色</a><br> <a href="#/green">綠色</a><br> <a href="#/red">紅色</a> </div> <script> // 建立Router構造函數 // currentHash爲當前hash值,routes爲路徑對象 function Router() { this.currentHash = '/'; this.routes = {}; } // 註冊路徑,每一個路徑對應一個回調函數。 Router.prototype.route = function (path, callback) { this.routes[path] = callback || function () { alert('未定義回調函數!'); } } // 更新頁面函數 Router.prototype.refresh = function () { this.currentHash = location.hash.slice(1) || '/'; this.routes[this.currentHash](); } // 初始化 Router.prototype.init = function () { var self = this; window.addEventListener('load', function () { self.refresh(); }, false); window.addEventListener('hashchange', function () { self.refresh(); }); } </script> <script> var wrap = document.querySelector('.router-wrap'); window.Router = new Router(); Router.route('/', function () { wrap.style.backgroundColor = '#fefefe'; }); Router.route('/black', function () { wrap.style.backgroundColor = 'black'; }); Router.route('/green', function () { wrap.style.backgroundColor = 'green'; }); Router.route('/red', function () { wrap.style.backgroundColor = 'red'; }); Router.init(); </script> </body> </html>
上面這段代碼的思路並不難理解。express
首先建立一個Router構造函數,初始化當前的url和一個routes對象。後端
接着定義了三個方法:api
效果以下:
上面使用的hash方法實現路由當然不錯,可是也是存在必定的問題的,就是太醜~ 若是在微信中使用,看不到url倒也無所謂,可是若是在通常的瀏覽器中使用就會遇到問題了。因此這裏使用 history api來實現。
在html5中的history api包括兩個方法history.pushState()和history.replaceState(),包含一個事件history.onpopstate,咱們先進行簡單的介紹:
history.pushState(stateObj, title, url)
pushState向瀏覽器的歷史記錄棧中壓入一個歷史記錄,因此這裏的push和pop就比較好理解了。
history.replaceState()
這個就比較好理解了,接受的參數都是同樣的,做用就是替換當前歷史記錄棧中的記錄。
在瀏覽器前進、後退時觸發,通常就是歷史記錄棧中的指針改變的時候就會觸發這個事件了。
在測試以前呢,須要知道這樣一個事情: 測試必須使用本地服務器上進行測試,若是使用file://的方式打開頁面,就會出現下面的狀況:
Uncaught SecurityError: A history state object with URL 'file:///C:/xxx/xxx/xxx/xxx.html' cannot be created in a document with origin 'null'.
由於pushState的url和當前的Url必須是同源的,而file://的形式是不存在同源的說法的,因此咱們必須用http://localhost的方式。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>router</title> <style> html, body { width: 100%; height: 100%; margin: 0; } div.router-wrap { width: 100%; height: 100%; background: #efefef; } a { display: inline-block; padding: 10px; color: pink; font-size: 25px; font-weight: bold; text-decoration: none; } </style> </head> <body> <div class="router-wrap"> <a href="/black" class="history-link">黑色</a><br> <a href="/green" class="history-link">綠色</a><br> <a href="/red" class="history-link">紅色</a> </div> <script> // 建立Router構造函數 function Router() { this.currentRoute = ''; this.routes = {}; this.init(); } // 註冊路由函數 Router.prototype.route = function (path, callback) { // 根據type類型,選擇相應的history api。 this.routes[path] = function (type) { if (type == 1) { history.pushState({path: path}, '', path); } else if (type == 2) { history.replaceState({path: path}, '', path); } callback(); } } // 更新頁面 Router.prototype.refresh = function (path, type) { this.routes[path](type); } // 初始化 Router.prototype.init = function () { var self = this; // 從新加載函數,這裏使用的主機是http://localhost:8088/ window.addEventListener('load', function () { self.currentRoute = location.href.slice(location.href.indexOf('/', 8)); console.log(self.currentRoute); self.refresh(self.currentRoute); }); // 當用戶點擊前進後退按鈕時觸發函數 window.addEventListener('popstate', function () { console.log('history.state.path:', history.state.path); self.currentRoute = history.state.path; self.refresh(self.currentRoute, 2); }, false); // 對全部的link標籤進行綁定事件 var historyLinks = document.querySelectorAll('.history-link'); for (var i = 0, len = historyLinks.length; i < len; i++) { historyLinks[i].onclick = function(e) { // 阻止默認 e.preventDefault(); self.currentRoute = e.target.getAttribute('href'); self.refresh(self.currentRoute, 1); } } } </script> <script> var wrap = document.querySelector('.router-wrap'); // 實例化Router window.Router = new Router(); // 註冊路由,實現相應功能 Router.route('/', function () { wrap.style.backgroundColor = '#efefef' }); Router.route('/black', function () { wrap.style.backgroundColor = 'black'; }); Router.route('/green', function () { wrap.style.backgroundColor = 'green'; }); Router.route('/red', function () { wrap.style.backgroundColor = 'red'; }); </script> </body> </html>
node部分的代碼以下:
var express = require('express'); var path = require('path') var app = express(); app.get('/', function (req, res) { res.sendFile(path.resolve(__dirname, './www/history.html')); }) app.get('*', function (req, res) { res.sendFile(path.resolve(__dirname, './www/history.html')); }); const port = 8088; var server = app.listen(port, function () { console.log("訪問地址爲 http://localhost:" + port) });
最終效果以下:
而且能夠實現基本的前進後退功能。
原創文章,未經容許,不得轉載!