hash 定义
hash 这个玩意是地址栏上 #及后面部分,代表网页中的一个位置,# 后面部分为位置标识符.页面打开后,会自动滚动到指定位置处.
位置标识符 ,一是使用锚点,比如 ,二是使用 id 属性,比如
<span id="demo" ></span>
带 hash 的请求
当打开 http://www.example.com/#print 服务器实际收到的请求地址是 http://www.example.com/ ,是不带 hash 值的.
那么你真想带 #字符咋办,转义啊, #转义字符为 #.也许有人会说,我咋知道这个转义啊,呵呵哒 encodeURIComponent.
hashchange 事件
从 The HashChangeEvent interface 可以看到 hashchange 事件的参数 HashChangeEvent 继承了 Event,仅仅多了两个属性
oldURL 先前会话历史记录的 URL
newURL 当前会话历史记录的 URL
简单的调用方式
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 的代码做个引子
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);
}
hash history 简单版本实现
从上面可以得知,我们的实现思路就是监听 hashchange 事件,这里先抛开兼容性问题.
1 首先监听 hashchange 事件,定义个 RouterManager 函数
bind(this) 让函数 this 指向 RouterManager 实例
取到 oldURL 和 newURL,同时查找一下是否注册,然后加载相关路由
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 定义添加,删除,加载,和初始化等方法
add 的时候,判断是不是 string, 如果是,重新构造一个新的 router 实例配置
load 这里主要是用来还原直接输入带 hash 的地址,比如 http://ex.com/#music
loadWithRouter 是最终渲染的入口
getByUrlOrName,你可以通过名字和 path 查找路由,name 是方便日后扩展
setIndex 设置默认路由地址
go, back, forward 同 history 的方法
init 里面会检测地址是不是带 hash,然后走不通的逻辑.history.replaceState 这是因为,如果不这么做, http://ex.com/ 跳转到 http://ex.com/#/music 会产生两条历史记录,这是我们不期望的.
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 公布函数
RouterManager.prototype.use = RouterManager.prototype.add
win.Router = RouterManager
4 页面怎么配置,简单的利用 a 标签 href
<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 注册,当然你也可以通过选择器批量注册
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 请求
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 地址 hash-Router1.0 7 源码地址 简单的前端 hash 路由 8 下一步
这就成了最简单最基本的路由了.让然还有很多要考虑,比如如下
动态路由匹配
嵌套路由
重定向和别名
错误捕捉
生命周期钩子
等等等
hash | CAN I USE
The HashChangeEvent interface
onhashchange | MDN
window.location.hash 使用说明
JS 单页面应用实现前端路由(hash)
Ajax 保留浏览器历史的两种解决方案(Hash&Pjax)
理解浏览器的历史记录
理解浏览器历史记录(2)-hashchange,pushState
web 开发中 前端路由 实现的几种方式和适用场景
来源: https://www.cnblogs.com/cloud-/p/8311057.html