hash这个玩意是地址栏上#及后面部分,表明网页中的一个位置,#后面部分为位置标识符。页面打开后,会自动滚动到指定位置处。
位置标识符 ,一是使用锚点,好比<a name="demo"></a>
,二是使用id属性,好比 <span id="demo" ></span>
javascript
当打开http://www.example.com/#print服务器实际收到的请求地址是http://www.example.com/,是不带hash值的。
那么你真想带#字符咋办,转义啊, #转义字符为%23。也许有人会说,我咋知道这个转义啊,呵呵哒encodeURIComponent。html
从The HashChangeEvent interface能够看到hashchange事件的参数HashChangeEvent继承了Event,仅仅多了两个属性前端
window.onhashchange = function(e){ console.log('old URL:', e.oldURL) console.log('new URL', e.newURL) }
hash | CAN I USE 上能够看到除了IE8一下和那个尴尬的Opera Mini,hashchange事件都是支持得很好。那么怎么作到兼容,用MDN的代码作个引子vue
function(window) { if ("onhashchange" in window) { return; } var location = window.location, oldURL = location.href, oldHash = location.hash; setInterval(function() { var newURL = location.href, newHash = location.hash; if (newHash != oldHash && typeof window.onhashchange === "function") { window.onhashchange({ type: "hashchange", oldURL: oldURL, newURL: newURL }); oldURL = newURL; oldHash = newHash; } }, 100); }
从上面能够得知,咱们的实现思路就是监听hashchange事件,这里先抛开兼容性问题。
1 首先监听hashchange事件,定义个RouterManager函数java
function RouterManager(list, index) { if (!(this instanceof RouterManager)) { return new RouterManager(arguments) } this.list = {} || list this.index = index this.pre = null this.current = null win.addEventListener('hashchange', function (ev) { var pre = ev.oldURL.split('#')[1], cur = ev.newURL.split('#')[1], preR = this.getByUrlOrName(pre), curR = this.getByUrlOrName(cur) this.loadWithRouter(curR, preR) }.bind(this)) }
2 定义添加,删除,加载,和初始化等方法react
RouterManager.prototype = { add: function (router, callback) { if (typeof router === 'string') { router = { path: router, name: router, callback: callback } } this.list[router.name || router.path] = router }, remove: function (name) { delete this.list[name] }, get: function (name) { return this.getByUrlOrName(name) }, load: function (name) { if (!name) { name = location.hash.slice(1) } var r = this.getByUrlOrName(name) this.loadWithRouter(r, null) }, loadWithRouter(cur, pre) { if (cur && cur.callback) { this.pre = this.current || cur cur.callback(cur, pre) this.current = cur } else { this.NOTFOUND('未找到相关路由') } }, getByUrlOrName: function (nameOrUrl) { var r = this.list[nameOrUrl] if (!r) { r = Object.values(this.list).find(rt => rt.name === nameOrUrl || rt.path === nameOrUrl) } return r }, setIndex: function (nameOrUrl) { this.indexRouter = this.getByUrlOrName(nameOrUrl) }, go: function (num) { win.history.go(num) }, back: function () { win.history.back() }, forward: function () { win.history.forward() }, init: function () { // 直接输入是带hash的地址,还原 if (win.location.hash) { /* 模拟事件 var ev = document.createEvent('Event') ev.initEvent('hashchange', true, true) ev.oldURL = ev.newURL = location.href win.dispatchEvent(ev) */ this.load() } else if (this.indexRouter) { // 是不带hash的地址,跳转到指定的首页 if ('replaceState' in win.history) { // 替换地址 win.history.replaceState(null, null, win.location.href + '#' + this.indexRouter.path) } else { win.location.hash = this.indexRouter.path } } } }
3 公布函数git
RouterManager.prototype.use = RouterManager.prototype.add win.Router = RouterManager
4 页面怎么配置,简单的利用a标签hrefgithub
<ul> <li> <li> <a href="#/m1">菜单1</a> </li> <ul> <li> <a href="#/m11">菜单11</a> </li> <li> <a href="#/m12">菜单12</a> </li> </ul> </li> <li> <a href="#/m2">菜单2</a> </li> <li> <a href="#/m3">菜单3</a> </li> </ul>
5 注册,固然你也能够经过选择器批量注册web
var router = new Router() router.NOTFOUND = function (msg) { content.innerHTML = msg } router.use('/m1', function (r) { req(r.path.slice(1)) }) router.use('/m11', function (r) { req(r.path.slice(1)) }) router.use('/m12', function (r) { req(r.path.slice(1)) }) router.use('/m2', function (r) { req(r.path.slice(1)) }) router.use('/m3', function (r) { req(r.path.slice(1)) }) router.setIndex('/m1') router.init()
为了方便演示,定义req,ajax方法,模拟ajax请求ajax
function req(url) { ajax(url, function (res) { content.innerHTML = res }) } function ajax(id, callback) { callback( { 'm1': '菜单1的主区域内容', 'm11': '菜单11的主区域内容', 'm12': '菜单12的主区域内容', 'm2': '菜单2的主区域内容', 'm3': '菜单3的主区域内容' }[id] || '404 Not Found!') }
6 demo地址
7 源码地址
简单的前端hash路由
8 下一步
这就成了最简单最基本的路由了。让然还有不少要考虑,好比以下
hash | CAN I USE
The HashChangeEvent interface
onhashchange | MDN
window.location.hash 使用说明
JS单页面应用实现前端路由(hash)
Ajax保留浏览器历史的两种解决方案(Hash&Pjax)
理解浏览器的历史记录
理解浏览器历史记录(2)-hashchange、pushState
Web开发中 前端路由 实现的几种方式和适用场景
本身动手写一个前端路由插件
vue-router
react-router