简单的基于hash和hashchange的前端路由
hash定义
hash这个玩意是地址栏上#及后面部分,代表网页中的一个位置,#后面部分为位置标识符。页面打开后,会自动滚动到指定位置处。
位置标识符 ,一是使用锚点,比如<a name="demo"></a>,二是使用id属性,比如 <span id="demo" ></span>
带hash的请求
当打开http://www.example.com/#print服务器实际收到的请求地址是http://www.example.com/,是不带hash值的。
那么你真想带#字符咋办,转义啊, #转义字符为%23。也许有人会说,我咋知道这个转义啊,呵呵哒encodeURIComponent。
hashchange事件
从The HashChangeEvent interface可以看到hashchange事件的参数HashChangeEvent继承了Event,仅仅多了两个属性
- oldURL 先前会话历史记录的URL
- newURL 当前会话历史记录的URL
简单的调用方式,
```js
window.onhashchange = function(e){
console.log('old URL:', e.oldURL)
console.log('new URL', e.newURL)
}
[hash | CAN I USE](https://caniuse.com/#search=hash) 上可以看到除了IE8一下和那个尴尬的Opera Mini,hashchange事件都是支持得很好。那么怎么做到兼容,用MDN的代码做个引子js
;(function(window) {
// exit if the browser implements that event
if ("onhashchange" in window) { return; }
var location = window.location,
oldURL = location.href,
oldHash = location.hash;
// check the location hash on a 100ms interval
setInterval(function() {
var newURL = location.href,
newHash = location.hash;
// if the hash has changed and a handler has been bound...
if (newHash != oldHash && typeof window.onhashchange === "function") {
// execute the handler
window.onhashchange({
type: "hashchange",
oldURL: oldURL,
newURL: newURL
});
oldURL = newURL;
oldHash = newHash;
}
}, 100);
})(window);
```
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地址
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
简单的基于hash和hashchange的前端路由的更多相关文章
- SPA中前端路由基本原理与实现方式
SPA 前端路由原理与实现方式 通常 SPA 中前端路由有2中实现方式,本文会简单快速总结这两种方法及其实现: 修改 url 中 Hash 利用 H5 中的 history Hash 我们都知道 ur ...
- 基于hash和pushState的网页前端路由实现
客户端路由 对于客户端(通常为浏览器)来说,路由的映射函数通常是进行一些DOM的显示和隐藏操作.这样,当访问不同的路径的时候,会显示不同的页面组件.客户端路由最常见的有以下两种实现方案:* 基于Has ...
- 前端路由hash、history原理及简单的实践下
阅读目录 一:什么是路由?前端有哪些路由?他们有哪些特性? 二:如何实现简单的hash路由? 三:如何实现简单的history路由? 四:hash和history路由一起实现 回到顶部 一:什么是路由 ...
- 前端路由以及浏览器回退,hash & history & location
一.前言 其实不止一次想监听浏览器的回退方法,比如 在 list.html 页滚动加载了几页列表,点到 detail.html 看详情,反回来时又得重新加载几页 H5 有背景音乐的,跳页就得重新放,体 ...
- 前端路由两种模式:hash、history
随着 ajax 的使用越来越广泛,前端的页面逻辑开始变得越来越复杂,特别是spa的兴起,前端路由系统随之开始流行. 从用户的角度看,前端路由主要实现了两个功能(使用ajax更新页面状态的情况下): 记 ...
- 前端路由的两种模式: hash 模式和 history 模式
随着 ajax 的使用越来越广泛,前端的页面逻辑开始变得越来越复杂,特别是spa的兴起,前端路由系统随之开始流行. 从用户的角度看,前端路由主要实现了两个功能(使用ajax更新页面状态的情况下): 记 ...
- 前端路由的两种模式:hash(#)模式和history模式(转)
随着 ajax 的使用越来越广泛,前端的页面逻辑开始变得越来越复杂,特别是spa的兴起,前端路由系统随之开始流行. 从用户的角度看,前端路由主要实现了两个功能(使用ajax更新页面状态的情况下): 记 ...
- 从零开始搭建一个简单的基于webpack的vue开发环境
原文地址:https://segmentfault.com/a/1190000012789253?utm_source=tag-newest 从零开始搭建一个简单的基于webpack的react开发环 ...
- 前端路由原理之 hash 模式和 history 模式
什么是路由? 个人理解路由就是浏览器 URL 和页面内容的一种映射关系. 比如你看到我这篇博客,博客的链接是一个 URL,而 URL 对应的就是我这篇博客的网页内容,这二者之间的映射关系就是路由. 其 ...
随机推荐
- [UWP]了解模板化控件(10):原则与技巧
1. 原则 推荐以符合以下原则的方式编写模板化控件: 选择合适的父类:选择合适的父类可以节省大量的工作,从UWP自带的控件中选择父类是最安全的做法,通常的选择是Control.ContentContr ...
- 初识Avro
Avro是Hadoop生态圈的一部分,由Hadoop的创始人Doug Cutting牵头开发,当前最新版本1.8.2.Avro是一个数据序列化系统,设计用于支持大批量数据交换的应用.它的主要特点有: ...
- 添加组groupadd,修改组groupmod,删除组groupdel,将用户加入删除组gpasswd
groupadd -g GID :指定组id groupmod -g GID :修改组id -n 新组名 :修改组名 groupmod -n newname oldname groupdel grou ...
- canvas 从初级到XX 2# 让我们在之前的基础之上,再迈进一步吧 [中级向] (上)
还是老样子,先啰嗦一点前言. 最近各种事务缠身,所以也就隔了比较长的时间才开始码这篇文.希望不会这么快就过气. 好了,接下来就开始码代码.(写到中途,突然感觉到的.本篇设计大量初中物理知识,请怀念的往 ...
- python for循环巧妙运用(迭代、列表生成式)
200 ? "200px" : this.width)!important;} --> 介绍 我们可以通过for循环来迭代list.tuple.dict.set.字符串,di ...
- 来腾讯云开发者实验室 学习.NET
腾讯云开发者实验室为开发者提供了一个零门槛的在线实验平台,开发者实验室提供的能力: 零门槛扫码即可免费领取实验机器,支持使用自有机器参与,实验完成后支持保留实验成果: 在线 WEB IDE 支持 sh ...
- C 函数参数 char **s与char *s[]
本文同时发表在https://github.com/zhangyachen/zhangyachen.github.io/issues/126 先来看一个小例子 : 编写函数遍历一个整型数组的元素,数组 ...
- express整合webpack的打包文件dist
对于我来说,第一次接触前后端整合问题的小白,刚开始是一脸懵逼,这个问题整整坑了我一个晚上加一个早上,现在写出来总结: 前端开发:vue-cli+webpack: 后台开发:nodejs框架expres ...
- JavaScript的简单入门
一.导读 简介:JavaScript简称js,是基于对象和事件驱动的脚本语言,主要运用于客户端.原名LiveScript,本身和Java没有任何关系,但语法上很类似. 特点:交互性(它可以做的就是信息 ...
- badboy 录制脚本并并发脚本
很久没有研究过接口相关的工具了,一个偶然的机会听说了 badboy,可以录制jemter脚本, 查了资料 还可以并发,于是乎,实践才知道. http://www.badboy.com.au/ 官网,我 ...