「这是我参与11月更文挑战的第16天,活动详情查看:2021最后一次更文挑战」
Javascript 前端路由的简单实现(hash模式和history模式)
路由是根据不同的url地址来显示不同的页面或内容的功能。 之前的路由主要是后端路由,对应不同的路由,服务端会返回相应的数据(html, json等等),然后浏览器重新解析和渲染。后端路由有一个很大的问题是路由切换的时候会频繁刷新页面,对用户体验来说并不友好。在这个背景之下,前端路由出现了,它可以实现路由切换同时不刷新页面。
在常用的前端框架(Vue, React 等)中,通常会有 hash 路由 和 history 路由两种路由方式。
- hash 路由:监听 url 中 hash 的变化,渲染不同的内容,这种路由不向服务器发送请求,不需要服务端的支持;
- history 路由:监听 url 中的路径(path)变化,渲染不同的内容,这种路由不向服务器发送请求,需要客户端和服务端共同的支持;
注意,这里的需要服务端的支持并不是指会向服务器发送请求,而是因为框架和history模式的特性,需要在服务端进行一些配置。如果服务端没有配置新更新的 url ,一刷新浏览器就会报错,因为刷新浏览器会真实地向服务器发送一个 http 的请求。因此若要使用 history 路由,需要服务端的支持。具体可以参考这一篇文章 vue-router history模式 为什么需要服务端配置以及如何配置
一、hash模式
hash模式主要是监听 url 中 hash 的变化,这里就必须提到一个关键的对象 window.location。
改变hash不会触发页面跳转,因为hash链接是当前页面中的某个片段,所以如果hash有变化,那么页面将会滚动到hash所连接的位置。但是页面中如果不存在hash对应的片段,则没有任何效果。
| 属性 | 含义 |
|---|---|
| location.href | 完整的url |
| location.protocol | 当前URL的协议,包括 : ; 比如 https: |
| location.host | 主机名和端口号 |
| location.hostname | 主机名 |
| location.port | 端口号 |
| location.pathname | url的路径部分,从 / 开始; |
| location.search | 查询参数,从 ? 开始 |
| location.hash | hash值,从 # 开始的 |
可以发现 href = protocol + host(hostname+port) + pathname + search + hash
hash模式主要用到的是location.hash
hash路由的特点:
- url中hash值的改变,不会重新加载页面。
- 通过hashchange事件可以监听到hash值的变化。
- hash值的改变会在浏览器的访问历史中增加一条记录。
注意,
- 事件
hashchange只会在 hash 发生变化时才能触发,而第一次加载页面时并不会触发这个事件,因此我们还需要监听load事件。 - 这两个事件的 event对象 是不一样的:hashchange 事件中的 event 对象有 oldURL 和 newURL 两个属性,可以从中提取出 preHash 和 currentHash 。但是 load 事件中的 event 没有这两个属性,不过我们可以通过 location.hash 来获取到当前的 hash 路由。
1. hash模式的简单实现
//hash路由
class HashRoute {
constructor() {
//存储对象
this.routes = {};
//当前hash
this.currentHash = ''
//绑定this.避免监听时this指向改变
this.freshRoute = this.freshRoute.bind(this);
//监听
// 页面加载事件
window.addEventListener('load', this.freshRoute, false);
// hashchange事件
window.addEventListener('hashmessage', this.freshRoute, false);
}
// 存储
storeRoute(path, callback) {
this.routes[path] = callback || function () { };
}
// 触发
freshRoute() {
this.currentHash = getHash();
let callback = this.routes[this.currentHash]
if (typeof callback === "function") callback()
}
// 获取当前hash值
getHash() {
return location.hash.slice(1) || '/';
}
}
二、history模式
history模式主要是监听 url 中 path 的变化,这里就必须提到两个关键的对象 window.location和window.history。
window.history对象的常用方法
| 方法 | 作用 |
|---|---|
| pushState(obj, title, url) | 前进到指定的 url,history栈会新增一条记录,不刷新页面 |
| replaceState(obj, title, url) | 用 url 替换当前的路由,history栈不会新增记录,不刷新页面 |
| forward() | 前进到下一个路由,如果存在的话 |
| back() | 后退到上一个路由 |
| go(number) | 进入到任意一个路由,正数为前进,负数为后退 |
history路由的特点:
- url中path值的改变,不会重新加载页面。
- 通过popstate事件可以监听到path值的变化。
注意,
- 和hash路由一样,
popstate事件只会在 history 发生变化时才能触发,而第一次加载页面时并不会触发这个事件,因此我们还需要监听load事件。 pushState和replaceState被调用时,不会触发触发 popstate 事件的,但是我们可以使用window.dispatchEvent来添加事件。
1. history模式的简单实现
//history路由
class HistoryRoutes {
constructor() {
//存储对象
this.routes = {};
//当前path
this.currentPath = '';
//绑定this.避免监听时this指向改变
this.freshRoute = this.freshRoute.bind(this);
//监听事件
window.addEventListener('load', this.freshRoute, false);
window.addEventListener('popstate', this.freshRoute, false);
window.addEventListener('pushState', this.freshRoute, false);
window.addEventListener('replaceState', this.freshRoute, false);
}
// 存储
storeRoute(path, callback) {
this.routes[path] = callback || function () { };
}
// 触发
freshRoute() {
this.currentPath = this.getState();
this.routes[this.currentPath] && this.routes[this.currentPath]();
}
// 初始化
init(path) {
history.replaceState(null, null, path);
this.routes[path] && this.routes[path]();
}
// 跳转
go(path) {
history.pushState(null, null, path);
this.routes[path] && this.routes[path]();
}
// 获取路由路径
getState() {
const path = window.location.pathname;
return path ? path : '/';
}
}