咱們可使用下面三種方法,來修改瀏覽器的地址javascript
修改如下location
對象的屬性值,會致使當前頁面從新加載vue
// 假如當前url爲:https://www.example.com/ // 把url修改成:https://www.example.com/?t=example location.search = '?t=example'; // 把url修改成:https://example.com/?t=example location.hostname = 'example.com'; // 把url修改成:https://www.example.com/example location.pathname = 'example'; // 把url修改成:https://www.example.com:8080 location.port = 8080
修改hash
時,瀏覽器歷史中會新增長一條記錄,可是並不會刷新頁面。所以SPA應用中,hash
也是一種切換路由的方式。java
// 假如當前url爲:https://www.example.com/ // 把url修改成:https://www.example.com/#example location.hash = '#example';
使用location.replace(url)
方法跳轉的url,並不會增長曆史記錄。react
使用location.reload()
方法能夠從新加載當前頁面,是否傳參的區別以下:ios
location.reload(); // 從新加載,多是從緩存加載 location.reload(true); // 從新加載,從服務器加載
使用go(n)
能夠在用戶記錄中沿任何方向導航(便可之前進也能夠後退)。正值表示在歷史中前進,負值表示在歷史中後退。git
假如要前進1頁,那麼可使用window.history.
`go(1)。同時,也可使用
window.history.forward()`來作相同的事情。
假如要後退1頁,那麼可使用window.history.
`go(-1)。同時,也可使用
window.history.back()`來作相同的事情。github
若是使用window.history.go(0)
或window.history.go()
都會從新加載當前頁面。正則表達式
上面咱們說到了,修改hash
能夠作到改變頁面的地址,在瀏覽器歷史中添加一條記錄,可是不會從新加載頁面。vue-router
咱們同時能夠配合hashchange
事件,監聽頁面地址hash
的變化。npm
使用history.pushState()
,相似是執行了location.href = url
,可是並不會從新加載頁面。假如用戶執行了後退操做,將會觸發popstate
事件。
使用history.replaceState()
,相似是執行了location.replace(url)
,可是並不會從新加載頁面。
注意,執行pushState、replaceState方法後,雖然瀏覽器地址有改變,可是並不會觸發popState事件
首先咱們須要肯定的是,咱們的路由應該須要下面4個屬性:
routes
:一個數組,包含全部註冊的路由對象mode
: 路由的模式,能夠選擇hash
或history
base
:根路徑constructor
:初始化新的路由實例class MiniRouter { constructor(options) { const { mode, routes, base } = options; this.mode = mode || (window.history.pushState ? 'history' : 'hash'); this.routes = routes || []; this.base = base || '/'; } } export default MiniRouter;
路由對象中包含下面兩個屬性
path
:由正則表達式表明的路徑地址(並非字符串,後面會詳細解釋)cb
:路由跳轉後執行的回調函數class MiniRouter { constructor(options) { const { mode, routes, base } = options; this.mode = mode || (window.history.pushState ? 'history' : 'hash'); this.routes = routes || []; this.base = base || '/'; } // 添加路由對象 👇 新增代碼 // routerConfig示例爲: // {path: /about/, cb(){console.log('about')}} addRoute(routeConfig) { this.routes.push(routeConfig); } /// 👆 新增代碼 } export default MiniRouter;
添加路由導航功能,其實是location
相關方法的封裝
詳細內容能夠回看:如何導航頁面?
class MiniRouter { constructor(options) { const { mode, routes, base } = options; this.mode = mode || (window.history.pushState ? 'history' : 'hash'); this.routes = routes || []; this.base = base || '/'; } addRoute(routeConfig) { this.routes.push(routeConfig); } // 添加前進、後退功能 👇 新增代碼 go(n) { window.location.go(n); } back() { window.location.back(); } forward() { window.location.forward(); } /// 👆 新增代碼 } export default MiniRouter;
參照vue-router,大橙子在這裏設計了push、replace兩種方法。其中:push
表明跳轉新頁面,並在歷史棧中增長一條記錄,用戶能夠後退replace
表明跳轉新頁面,可是不在歷史棧中增長記錄,用戶不能夠後退
若是是hash模式下
使用location.hash = newHash
來實現push跳轉
使用window.location.replace(url)
來實現replace跳轉
若是是history模式下
使用history.pushState()
來實現push跳轉
使用history.replaceState()
來實現replace跳轉
請注意:
爲pushState
與replaceState
添加try...catch
是因爲Safari的某個安全策略有興趣的同窗能夠查看
vue-router相關commit
Stack Overflow上的相關問題
class MiniRouter { constructor(options) { const { mode, routes, base } = options; this.mode = mode || (window.history.pushState ? 'history' : 'hash'); this.routes = routes || []; this.base = base || '/'; } addRoute(routeConfig) { this.routes.push(routeConfig); } go(n) { window.history.go(n); } back() { window.location.back(); } forward() { window.location.forward(); } // 實現導航到新路由的功能 // push表明跳轉新頁面,並在歷史棧中增長一條記錄,用戶能夠後退 // replace表明跳轉新頁面,可是不在歷史棧中增長記錄,用戶不能夠後退 //👇 新增代碼 push(url) { if (this.mode === 'hash') { this.pushHash(url); } else { this.pushState(url); } } pushHash(path) { window.location.hash = path; } pushState(url, replace) { const history = window.history; try { if (replace) { history.replaceState(null, null, url); } else { history.pushState(null, null, url); } this.handleRoutingEvent(); } catch (e) { window.location[replace ? 'replace' : 'assign'](url); } } replace(path) { if (this.mode === 'hash') { this.replaceHash(path); } else { this.replaceState(path); } } replaceState(url) { this.pushState(url, true); } replaceHash(path) { window.location.replace(`${window.location.href.replace(/#(.*)$/, '')}#${path}`); } /// 👆 新增代碼 } export default MiniRouter;
在history
模式下,咱們會使用location.path
來獲取當前連接路徑。
若是設置了base
參數,將會把base路徑幹掉,方便後面匹配路由地址。
在hash
模式下,咱們會使用正則匹配將#
後的地址匹配出來。
固然全部操做以後,將會把/
徹底去掉。
class MiniRouter { constructor(options) { const { mode, routes, base } = options; this.mode = mode || (window.history.pushState ? 'history' : 'hash'); this.routes = routes || []; this.base = base || '/'; } addRoute(routeConfig) { this.routes.push(routeConfig); } go(n) { window.history.go(n); } back() { window.location.back(); } forward() { window.location.forward(); } push(url) { if (this.mode === 'hash') { this.pushHash(url); } else { this.pushState(url); } } pushHash(path) { window.location.hash = path; } pushState(url, replace) { const history = window.history; try { if (replace) { history.replaceState(null, null, url); } else { history.pushState(null, null, url); } this.handleRoutingEvent(); } catch (e) { window.location[replace ? 'replace' : 'assign'](url); } } replace(path) { if (this.mode === 'hash') { this.replaceHash(path); } else { this.replaceState(path); } } replaceState(url) { this.pushState(url, true); } replaceHash(path) { window.location.replace(`${window.location.href.replace(/#(.*)$/, '')}#${path}`); } // 實現獲取路徑功能 //👇 新增代碼 getPath() { let path = ''; if (this.mode === 'history') { path = this.clearSlashes(decodeURI(window.location.pathname)); path = this.base !== '/' ? path.replace(this.base, '') : path; } else { const match = window.location.href.match(/#(.*)$/); path = match ? match[1] : ''; } // 可能還有多餘斜槓,所以須要再清除一遍 return this.clearSlashes(path); }; clearSlashes(path) { return path.toString().replace(/\/$/, '').replace(/^\//, ''); } /// 👆 新增代碼 } export default MiniRouter;
在實例化路由時,咱們將會按照mode的不一樣,在頁面上掛載不一樣的事件監聽器:
hashchange
事件進行監聽popstate
事件進行監聽在監聽到變化後,回調方法將會遍歷咱們的路由表,若是符合路由的正則表達式,就執行相關路由的回調方法。
class MiniRouter { constructor(options) { const { mode, routes, base } = options; this.mode = mode || (window.history.pushState ? 'history' : 'hash'); this.routes = routes || []; this.base = base || '/'; this.setupListener(); // 👈 新增代碼 } addRoute(routeConfig) { this.routes.push(routeConfig); } go(n) { window.history.go(n); } back() { window.location.back(); } forward() { window.location.forward(); } push(url) { if (this.mode === 'hash') { this.pushHash(url); } else { this.pushState(url); } } pushHash(path) { window.location.hash = path; } pushState(url, replace) { const history = window.history; try { if (replace) { history.replaceState(null, null, url); } else { history.pushState(null, null, url); } this.handleRoutingEvent(); } catch (e) { window.location[replace ? 'replace' : 'assign'](url); } } replace(path) { if (this.mode === 'hash') { this.replaceHash(path); } else { this.replaceState(path); } } replaceState(url) { this.pushState(url, true); } replaceHash(path) { window.location.replace(`${window.location.href.replace(/#(.*)$/, '')}#${path}`); } getPath() { let path = ''; if (this.mode === 'history') { path = this.clearSlashes(decodeURI(window.location.pathname)); path = this.base !== '/' ? path.replace(this.base, '') : path; } else { const match = window.location.href.match(/#(.*)$/); path = match ? match[1] : ''; } // 可能還有多餘斜槓,所以須要再清除一遍 return this.clearSlashes(path); }; clearSlashes(path) { return path.toString().replace(/\/$/, '').replace(/^\//, ''); } // 實現監聽路由,及處理回調功能 //👇 新增代碼 setupListener() { this.handleRoutingEvent(); if (this.mode === 'hash') { window.addEventListener('hashchange', this.handleRoutingEvent.bind(this)); } else { window.addEventListener('popstate', this.handleRoutingEvent.bind(this)); } } handleRoutingEvent() { if (this.current === this.getPath()) return; this.current = this.getPath(); for (let i = 0; i < this.routes.length; i++) { const match = this.current.match(this.routes[i].path); if (match) { match.shift(); this.routes[i].cb.apply({}, match); return; } } } /// 👆 新增代碼 } export default MiniRouter;
實例化以前實現的MiniRouter
,是否是和日常寫的router很像(除了功能少了不少😝)?
相關代碼以下:
import MiniRouter from './MiniRouter'; const router = new MiniRouter({ mode: 'history', base: '/', routes: [ { path: /about/, cb() { app.innerHTML = `<h1>這裏是關於頁面</h1>`; } }, { path: /news\/(.*)\/detail\/(.*)/, cb(id, specification) { app.innerHTML = `<h1>這裏是新聞頁</h1><h2>您正在瀏覽id爲${id}<br>渠道爲${specification}的新聞</h2>`; } }, { path: '', cb() { app.innerHTML = `<h1>歡迎來到首頁!</h1>`; } } ] });
完整的代碼,請跳轉至:github傳送門
下載代碼後,執行下面的代碼,進行調試:
npm i npm run dev
常見的react-router
和vue-router
傳入的路徑都是字符串,而上面實現的例子中,使用的是正則表達式。那麼如何才能作到解析字符串呢?
看看這兩個開源路由,咱們都不難發現,它們都使用了path-to-regexp這個庫。假如咱們傳入了一個路徑:
/news/:id/detail/:channel
使用match方法
import { match } from "path-to-regexp"; const fn = match("/news/:id/detail/:channel", { decode: decodeURIComponent }); // {path: "/news/122/detail/baidu", index: 0, params: {id: "122", channel: "baidu"}} console.log(fn("/news/122/detail/baidu")); // false console.log(fn("/news/122/detail"));
是否是很眼熟?和咱們日常使用路由庫時,使用相關參數的路徑一致。有興趣的同窗,能夠沿着這個思路將路由優化一下
咱們發現,當知足咱們愈來愈多需求的時候,代碼庫也變得愈來愈龐大。可是最核心的內容,永遠只有那一些,主要抓住了主線,實際上分支的理解就會簡單起來。
本文的代碼主要參考自開源做者navigo的文章,在此基礎上,爲了貼合vue-router的相關配置。作了一些改動,因爲水平受限,文內若有錯誤,還望你們在評論區內提出,以避免誤人子弟。
navigo
的做者。建議能夠讀一讀原文,一樣能夠啓發你的思惟。