前端路由的两种方式:Hash和History
Hash 模式是使用 URL 的 Hash 来模拟一个完整的 URL,因此当 URL 改变的时候页面并不会重载。History 模式则会直接改变 URL,所以在路由跳转的时候会丢失一些地址信息,在刷新或直接访问路由地址的时候会匹配不到静态资源。因此需要在服务器上配置一些信息,让服务器增加一个覆盖所有情况的候选资源,比如跳转index.html什么的,一般来说是app 依赖的页面
Hash原理: Hash方法是在路由中带有一个#,主要原理是通过监听#后的URL路径标识符的更改而触发的浏览器hashchange事件,然后通过获取location.hash得到当前的路径标识符,再进行一些路由跳转的操作MDN。
如下:把目标路由和对应的回调记录下来,点击跳转触发 。
1 class RouterClass { 2 constructor() { 3 this.isBack = false 4 this.routes = {} // 记录路径标识符对应的cb 5 this.currentUrl = '' // 记录hash只为方便执行cb 6 this.historyStack = [] // hash栈 7 window.addEventListener('load', () => this.render()) 8 window.addEventListener('hashchange', () => this.render()) 9 } 10 11 /** 12 * 初始化 13 */ 14 static init() { 15 window.Router = new RouterClass() 16 } 17 18 /** 19 * 记录path对应cb 20 * @param path 21 * @param cb 回调 22 */ 23 route(path, cb) { 24 this.routes[path] = cb || function() {} 25 } 26 27 /** 28 * 入栈当前hash,执行cb 29 */ 30 render() { 31 if (this.isBack) { // 如果是由backoff进入,则置false之后return 32 this.isBack = false // 其他操作在backoff方法中已经做了 33 return 34 } 35 this.currentUrl = location.hash.slice(1) || '/' 36 this.historyStack.push(this.currentUrl) 37 this.routes[this.currentUrl]() 38 // console.log('refresh事件 Stack:', this.historyStack, ' currentUrl:', this.currentUrl) 39 } 40 41 /** 42 * 路由后退 43 */ 44 back() { 45 this.isBack = true 46 this.historyStack.pop() // 移除当前hash,回退到上一个 47 const { length } = this.historyStack 48 if (!length) return 49 let prev = this.historyStack[length - 1] // 拿到要回退到的目标hash 50 location.hash = `#${ prev }` 51 this.currentUrl = prev 52 this.routes[prev]() // 执行对应cb 53 // console.log('点击后退,当前stack:', this.historyStack, ' currentUrl:', this.currentUrl) 54 } 55 } 56 57 58 RouterClass.init() 59 const BtnDom = document.querySelector('button') 60 const ContentDom = document.querySelector('.content-div') 61 const changeContent = content => ContentDom.innerHTML = content 62 63 Router.route('/', () => changeContent('默认页面')) 64 Router.route('/page1', () => changeContent('page1页面')) 65 Router.route('/page2', () => changeContent('page2页面')) 66 67 BtnDom.addEventListener('click', Router.back.bind(Router), false)
popstate:事件:当活动的历史记录发生变化,就会触发popstate事件,在点击浏览器的前进后退按钮或者调用上面前三个方法的时候也会触发;MDN
- history.pushState()
- history.popstate()
- history.replaceState()
- history.forward() (history.go(1))
- history.back() (history.go(-1))
1 class RouterClass { 2 constructor(path) { 3 this.routes = {} // 记录路径标识符对应的cb 4 history.replaceState({ path }, null, path) 5 this.routes[path] && this.routes[path]() 6 window.addEventListener('popstate', e => { 7 console.log(e, ' --- e') 8 const path = e.state && e.state.path 9 this.routes[path] && this.routes[path]() 10 }) 11 } 12 13 /** 14 * 初始化 15 */ 16 static init() { 17 window.Router = new RouterClass(location.pathname) 18 } 19 20 /** 21 * 记录path对应cb 22 * @param path 路径 23 * @param cb 回调 24 */ 25 route(path, cb) { 26 this.routes[path] = cb || function() {} 27 } 28 29 /** 30 * 触发路由对应回调 31 * @param path 32 */ 33 go(path) { 34 history.pushState({ path }, null, path) 35 this.routes[path] && this.routes[path]() 36 } 37 } 38 39 40 RouterClass.init() 41 const ul = document.querySelector('ul') 42 const ContentDom = document.querySelector('.content-div') 43 const changeContent = content => ContentDom.innerHTML = content 44 45 Router.route('/', () => changeContent('默认页面')) 46 Router.route('/page1', () => changeContent('page1页面')) 47 Router.route('/page2', () => changeContent('page2页面')) 48 49 ul.addEventListener('click', e => { 50 if (e.target.tagName === 'A') { 51 e.preventDefault() 52 Router.go(e.target.getAttribute('href')) 53 } 54 })