路由這概念最開始是在後端出現的,在之前先後端不分離的時候,由後端來控制路由,服務器接收客戶端的請求,解析對應的url路徑,並返回對應的頁面/資源。簡單的說 路由就是根據不一樣的url地址來展現不一樣的內容或頁面.javascript
在好久好久之前~ 用戶的每次更新操做都須要從新刷新頁面,很是的影響交互體驗,後來,爲了解決這個問題,便有了Ajax(異步加載方案),Ajax給體驗帶來了極大的提高。雖然Ajax解決了用戶交互時體驗的痛點,可是多頁面之間的跳轉同樣會有很差的體驗,因此便有了spa(single-page application)使用的誕生。而spa應用即是基於前端路由實現的,因此便有了前端路由。html
現在比較火的vue-router/react-router 也是基於前端路由的原理實現的~前端
window對象提供了onhashchange事件來監聽hash值的改變,一旦url中的hash值發生改變,便會觸發該事件。
window.onhashchange = function(){ // hash 值改變 // do you want }
HTML5的History API 爲瀏覽器的全局history對象增長的擴展方法。簡單來講,history其實就是瀏覽器歷史棧的一個接口。這裏不細說history的每一個API啦。具體可查閱 傳送門 vue
window對象提供了onpopstate事件來監聽歷史棧的改變,一旦歷史棧信息發生改變,便會觸發該事件。java
須要特別注意的是,調用history.pushState()或history.replaceState()不會觸發popstate事件。只有在作出瀏覽器動做時,纔會觸發該事件。react
window.onpopstate = function(){ // 歷史棧 信息改變 // do you want }
history提供了兩個操做歷史棧的API:history.pushState 和 history.replaceState
history.pushState(data[,title][,url]);//向歷史記錄中追加一條記錄
history.replaceState(data[,title][,url]);//替換當前頁在歷史記錄中的信息。
// data: 一個JavaScript對象,與用pushState()方法建立的新歷史記錄條目關聯。不管什麼時候用戶導航到新建立的狀態,popstate事件都會被觸發,而且事件對象的state屬性都包含歷史記錄條目的狀態對象的拷貝。 //title: FireFox瀏覽器目前會忽略該參數,雖然之後可能會用上。考慮到將來可能會對該方法進行修改,傳一個空字符串會比較安全。或者,你也能夠傳入一個簡短的標題,標明將要進入的狀態。 //url: 新的歷史記錄條目的地址。瀏覽器不會在調用pushState()方法後加載該地址,但以後,可能會試圖加載,例如用戶重啓瀏覽器。新的URL不必定是絕對路徑;若是是相對路徑,它將以當前URL爲基準;傳入的URL與當前URL應該是同源的,不然,pushState()會拋出異常。該參數是可選的;不指定的話則爲文檔當前URL。
對比 | Hash | History |
---|---|---|
觀賞性 | 醜 | 美 |
兼容性 | >ie8 | >ie10 |
實用性 | 直接使用 | 需後端配合 |
命名空間 | 同一document | 同源 |
本demo只是想說幫助咱們經過實踐更進一步的理解前端路由這個概念,因此只作了簡單的實現~
當咱們使用history模式時,若是沒有進行配置,刷新頁面會出現404。緣由是由於history模式的url是真實的url,服務器會對url的文件路徑進行資源查找,找不到資源就會返回404。 webpack
這個問題的解決方案這裏就不細說了,google一下,你就知道~ 咱們在如下demo使用webpack-dev-server的裏的historyApiFallback屬性來支持HTML5 History Mode。git
|-- package.json |-- webpack.config.js |-- index.html |-- src |-- index.js |-- routeList.js |-- base.js |-- hash.js |-- history.js
廢話很少說,直接上代碼~github
package.jsonweb
{ "name": "web_router", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "dev": "webpack-dev-server --config ./webpack.config.js" }, "author": "webfansplz", "license": "MIT", "devDependencies": { "html-webpack-plugin": "^3.2.0", "webpack": "^4.28.1", "webpack-cli": "^3.2.1", "webpack-dev-server": "^3.1.14" } }
webpack.config.js
'use strict'; const path = require('path'); const webpack = require('webpack'); const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { mode: 'development', entry: './src/index.js', output: { filename: '[name].js' }, devServer: { clientLogLevel: 'warning', hot: true, inline: true, open: true, //在開發單頁應用時很是有用,它依賴於HTML5 history API,若是設置爲true,全部的跳轉將指向index.html (解決histroy mode 404) historyApiFallback: true, host: 'localhost', port: '6789', compress: true }, plugins: [ new webpack.HotModuleReplacementPlugin(), new HtmlWebpackPlugin({ filename: 'index.html', template: 'index.html', inject: true }) ] };
首先咱們先初始化定義咱們須要實現的功能及配置參數。
前端路由 | 參數 | 方法 |
---|---|---|
x | 模式(mode) | push(壓入) |
x | 路由列表(routeList) | replace(替換) |
x | x | go(前進/後退) |
const MODE=''; const ROUTELIST=[]; class WebRouter { constructor() { } push(path) { ... } replace(path) { ... } go(num) { ... } } new WebRouter({ mode: MODE, routeList: ROUTELIST });
前面咱們說了前端路由有兩種實現方式。
1.定義路由列表
2.咱們分別爲這兩種方式建立對應的類,並根據不一樣的mode參數進行實例化,完成webRouter類的實現。
export const ROUTELIST = [ { path: '/', name: 'index', component: 'This is index page' }, { path: '/hash', name: 'hash', component: 'This is hash page' }, { path: '/history', name: 'history', component: 'This is history page' }, { path: '*', name: 'notFound', component: '404 NOT FOUND' } ];
export class HashRouter{ }
export class HistoryRouter{ }
import { HashRouter } from './hash'; import { HistoryRouter } from './history'; import { ROUTELIST } from './routeList'; //路由模式 const MODE = 'hash'; class WebRouter { constructor({ mode = 'hash', routeList }) { this.router = mode === 'hash' ? new HashRouter(routeList) : new HistoryRouter(routeList); } push(path) { this.router.push(path); } replace(path) { this.router.replace(path); } go(num) { this.router.go(num); } } const webRouter = new WebRouter({ mode: MODE, routeList: ROUTELIST });
前面咱們已經實現了webRouter的功能,接下來咱們來實現兩種方式。
由於兩種模式都須要調用一個方法來實現不一樣路由內容的刷新,so~
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta http-equiv="X-UA-Compatible" content="ie=edge" /> <title>前端路由</title> </head> <body> <div id="page"></div> </body> </html>
const ELEMENT = document.querySelector('#page'); export class BaseRouter { //list = 路由列表 constructor(list) { this.list = list; } render(state) { //匹配當前的路由,匹配不到則使用404配置內容 並渲染~ let ele = this.list.find(ele => ele.path === state); ele = ele ? ele : this.list.find(ele => ele.path === '*'); ELEMENT.innerText = ele.component; } }
ok,下面咱們來實現兩種模式。
Hash模式
import { BaseRouter } from './base.js'; export class HashRouter extends BaseRouter { constructor(list) { super(list); this.handler(); //監聽hash變化事件,hash變化從新渲染 window.addEventListener('hashchange', e => { this.handler(); }); } //渲染 handler() { this.render(this.getState()); } //獲取當前hash getState() { const hash = window.location.hash; return hash ? hash.slice(1) : '/'; } //獲取完整url getUrl(path) { const href = window.location.href; const i = href.indexOf('#'); const base = i >= 0 ? href.slice(0, i) : href; return `${base}#${path}`; } //改變hash值 實現壓入 功能 push(path) { window.location.hash = path; } //使用location.replace實現替換 功能 replace(path) { window.location.replace(this.getUrl(path)); } //這裏使用history模式的go方法進行模擬 前進/後退 功能 go(n) { window.history.go(n); } }
History模式
import { BaseRouter } from './base.js'; export class HistoryRouter extends BaseRouter { constructor(list) { super(list); this.handler(); //監聽歷史棧信息變化,變化時從新渲染 window.addEventListener('popstate', e => { this.handler(); }); } //渲染 handler() { this.render(this.getState()); } //獲取路由路徑 getState() { const path = window.location.pathname; return path ? path : '/'; } //使用pushState方法實現壓入功能 //PushState不會觸發popstate事件,因此須要手動調用渲染函數 push(path) { history.pushState(null, null, path); this.handler(); } //使用replaceState實現替換功能 //replaceState不會觸發popstate事件,因此須要手動調用渲染函數 replace(path) { history.replaceState(null, null, path); this.handler(); } go(n) { window.history.go(n); } }
就這樣,一個簡單的前端路由就完成拉。
若是以爲有幫助到你的話,給個star哈~