基于hash实现前端路由
前端路由分为两部分,hash 路由和 History 路由,例如我们常用的 vue-roter 就包含这两部分,这里并不探讨框架是如何进行封装的,而是使用原生的 api 来实现这样一个功能。
预计分为两部分,这里先介绍 hash 路由,实现基本的接受响应和前进后退(为了方便,下面代码不做任何兼容处理)
hash 指的就是 url 标识符后面#
号部分的内容(包含#),例如:https://xxx#abc
这个 url 的 hash 就是#abc
。
而 hash 路由就是指接收 hash 的变化更新对应的路由视图,它的优点就是兼容性很好,在 ie 下也能正常工作,不足之处就是#
这个符号很丑陋。
监听 hash 变化
window 对象上有hashchange
事件可以监听到 hash 的变化,我们先拿来用用,看看好不好用。
class Router {
constructor() {
this.hash = new Map();
this.change = () => {
const href = this.getHash();
this.move(href);
};
window.addEventListener("hashchange", this.change);
}
route(href, fn) {
this.hash.set(href, fn);
}
move(href) {
if (!this.hash.has(href)) {
return false;
}
const fn = this.hash.get(href);
if (typeof fn == "function") {
fn.call(this, href);
}
return true;
}
getHash() {
return location.hash ? location.hash.slice(1) : "/";
}
}
使用方法
const color = {
hash1: "#333",
hash2: "#666",
hash3: "#DDD"
};
const route = new Router();
route.route("hash1", function(e) {
document.body.style.background = color[e];
});
route.route("hash2", function(e) {
document.body.style.background = color[e];
});
route.route("hash3", function(e) {
document.body.style.background = color[e];
});
这里已经实现了 hash 响应了,不过还是有一个问题就是如果点击 hash 之后刷新浏览器,对应的回调函数并不会执行,所以我们还需要浏览器加载完成后响应对应的回调函数,修订这个问题很简单监听 load 事件即可。
// 省略其他代码
constructor() {
window.addEventListener('load', this.change);
window.addEventListener("hashchange", this.change);
}
后退功能
上面完成了基础功能,下面对这个代码进行改造,首先新增一个后退处理。
实现的思路是新增一个history
数组来储存变化的 hash,并且创建一个指针,在后退的时候移动指针变化,同时触发对应的函数。
class Router {
constructor() {
this.hash = new Map();
// 储存hash变化的数组
this.history = [];
// 指针
this.pointer = this.history.length - 1;
this.change = () => {
const href = this.getHash();
if (!this.move(href)) {
return console.error(`hash路由${href}无对应处理函数`);
}
// 在路由发生变化的时候,同时对history添加,和移动指针
this.history.push(href);
this.pointer++;
};
// 后退
this.backOff = () => {
// 数组下表不能负
if (this.pointer <= 0) {
this.pointer = 0;
} else {
// 移动下标
this.pointer -= 1;
}
// 读取对应的值,移动hash
const href = this.history[this.pointer];
console.log(href, this.pointer, this.history);
this.setHash(href);
this.move(href);
};
window.addEventListener("load", this.change);
window.addEventListener("hashchange", this.change);
}
route(href, fn) {
this.hash.set(href, fn);
}
move(href) {
if (!this.hash.has(href)) {
return false;
}
const fn = this.hash.get(href);
if (typeof fn == "function") {
fn.call(this, href);
}
return true;
}
getHash() {
// 过滤掉'#'
return location.hash ? location.hash.slice(1) : "/";
}
setHash(href) {
if (!href) {
return;
}
if (!/^/.test(href)) {
href = `#${href}`;
}
location.hash = href;
}
}
上面看似实现了后退功能,不过仔细观察上面的代码,我们会发现这样实现是有问题的
- 回调函数被执行两次;
history
数组记录的 hash 不对,因为后退是不需要记录的;
下面来修正这两个问题。
class Router {
constructor() {
this.hash = new Map();
// 储存hash变化的数组
this.history = [];
// 指针
this.pointer = this.history.length - 1;
// 是否后退,默认给false
this.isBackOff = false;
this.change = () => {
const href = this.getHash();
if (!this.hash.has(href)) {
return console.error(`hash路由${href}无对应处理函数`);
}
if (!this.isBackOff) {
// 看一下指针长度对不对,如果多余就截取掉
if (this.pointer < this.history.length - 1) {
this.history = this.history.slice(0, this.pointer + 1);
}
// 在路由发生变化的时候,同时对history添加,和移动指针
this.history.push(href);
this.pointer++;
}
this.move(href);
this.isBackOff = false;
};
// 后退
this.backOff = () => {
this.isBackOff = true;
// 数组下表不能负
if (this.pointer <= 0) {
this.pointer = 0;
} else {
// 移动下标
this.pointer -= 1;
}
// 读取对应的值,移动hash
const href = this.history[this.pointer];
console.log(href, this.pointer, this.history);
this.setHash(href);
};
window.addEventListener("load", this.change);
window.addEventListener("hashchange", this.change);
}
route(href, fn) {
this.hash.set(href, fn);
}
move(href) {
const fn = this.hash.get(href);
if (typeof fn == "function") {
fn.call(this, href);
}
}
getHash() {
// 过滤掉'#'
return location.hash ? location.hash.slice(1) : "/";
}
setHash(href) {
if (!href) {
return;
}
if (!/^/.test(href)) {
href = `#${href}`;
}
location.hash = href;
}
}
借助变量isBackOff
来控制,如果是后退只执行回调函数,当正常操作的时候新增history
数据,同时清楚多余数组,前进跟后退类似,下面就来实现它。
前进
思路跟后退的一致,借助变量来进行控制,定义一个前进方法。
class Router {
constructor() {
this.hash = new Map();
// 储存hash变化的数组
this.history = [];
// 指针
this.pointer = this.history.length - 1;
// 是否后退,默认给false
this.isBackOff = false;
// 前进标识
this.isForward = false;
this.change = () => {
const href = this.getHash();
if (!this.hash.has(href)) {
return console.error(`hash路由${href}无对应处理函数`);
}
if (!this.isBackOff && !this.isForward) {
// 看一下指针长度对不对,如果多余就截取掉
if (this.pointer < this.history.length - 1) {
this.history = this.history.slice(0, this.pointer + 1);
}
// 在路由发生变化的时候,同时对history添加,和移动指针
this.history.push(href);
this.pointer++;
}
this.move(href);
this.isBackOff = false;
this.isForward = false;
};
// 后退
this.backOff = () => {
this.isBackOff = true;
// 数组下表不能负
if (this.pointer <= 0) {
this.pointer = 0;
} else {
// 移动下标
this.pointer -= 1;
}
// 读取对应的值,移动hash
const href = this.history[this.pointer];
console.log(href, this.pointer, this.history);
this.setHash(href);
};
// 前进
this.forward = () => {
this.isForward = true;
// 数组下表不能负
if (this.pointer >= this.history.length - 1) {
this.pointer = this.history.length - 1;
} else {
// 移动下标
this.pointer += 1;
}
// 读取对应的值,移动hash
const href = this.history[this.pointer];
console.log(`前进${href}`, this.pointer, this.history);
this.setHash(href);
};
window.addEventListener("load", this.change);
window.addEventListener("hashchange", this.change);
}
route(href, fn) {
this.hash.set(href, fn);
}
move(href) {
const fn = this.hash.get(href);
if (typeof fn == "function") {
fn.call(this, href);
}
}
getHash() {
// 过滤掉'#'
return location.hash ? location.hash.slice(1) : "/";
}
setHash(href) {
if (!href) {
return;
}
if (!/^/.test(href)) {
href = `#${href}`;
}
location.hash = href;
}
}
跟后退对比,实质上也只是把后退指针改成前进了,如果想要实现go
跳转的功能也跟前进、后退思路一样。
使用方法
<ul>
<li><a href="#hash1">hash1</a></li>
<li><a href="#hash2">hash2</a></li>
<li><a href="#hash3">hash3</a></li>
</ul>
<div><button class="f">前进</button> <button class="b">后退</button></div>
1
const color = {
hash1: "#333",
hash2: "#666",
hash3: "#DDD"
};
const route = new Router();
route.route("hash1", function(e) {
document.body.style.background = color[e];
});
route.route("hash2", function(e) {
document.body.style.background = color[e];
});
route.route("hash3", function(e) {
document.body.style.background = color[e];
});
const backOff = document.querySelector(".b");
const forward = document.querySelector(".f");
backOff.addEventListener("click", route.backOff);
forward.addEventListener("click", route.forward);
最后
这里实现了 hash 路由的响应变化
和前进
、后退
、404
的功能,但是还有很多地方需要完善,比如go
跳转。
下一节将介绍 History 路由的实现
,如果喜欢请点击一下 star