跳到主要内容

浏览器-前端路由

所谓的前端路由, 是在SPA应用中, 前端自身接管路由切换视图的能力. 一般来说, 路由需要实现三个功能:

  1. 当浏览器的地址变化时, 切换页面
  2. 浏览器后退, 前进的时候, 网页内容跟随变化
  3. 刷新浏览器, 网页加载当前路由对应的内容

前端路由主要有两种模式:

  1. hash 模式: 监听hashchange事件实现 dom 变化

image

  1. history: 使用 h5 的 history 的 api

image

一个简单的 hash 路由

hash 路由一个明显的表示是带有"#", 通过监听 url 中 hash 的变化来进行路由跳转

hash 的优势就是兼容性更好

实现大致如下:

  1. 初始化一个路由
  2. 实现路由 hash 存储与执行
    1. 将路由的 hash 以及对应的 call 函数存储
    2. 触发路由 hash 变化后, 执行对应的 callback 函数
  3. 监听对应的事件
class Routers {
constructor() {
this.routes = {};
this.currentUrl = '';
this.refresh = this.refresh.bind(this);
window.addEventListener('load', this.refresh, false);
window.addEventListener('hashchange', this.refresh, false);
}

route(path, callback) {
this.routes[path] = callback || function() {};
}

refresh() {
this.currentUrl = location.hash.slice(1) || '/';
this.routes[this.currentUrl]();
}
}

接下来实现后退:

  1. 创建一个数组来存储过往的 hash 路由, 并且创建一个指针来移动指向不同的 hash 路由
  2. 添加判断: 如果是后退的话,我们只需要执行回调函数,不需要添加数组和移动指针。
class Routers {
constructor() {
// 储存hash与callback键值对
this.routes = {};
// 当前hash
this.currentUrl = '';
// 记录出现过的hash
this.history = [];
// 作为指针,默认指向this.history的末尾,根据后退前进指向history中不同的hash
this.currentIndex = this.history.length - 1;
this.refresh = this.refresh.bind(this);
this.backOff = this.backOff.bind(this);
// 默认不是后退操作
this.isBack = false;

window.addEventListener('load', this.refresh, false);
window.addEventListener('hashchange', this.refresh, false);
}

route(path, callback) {
this.routes[path] = callback || function() {};
}

refresh() {
this.currentUrl = location.hash.slice(1) || '/';
if (!this.isBack) {
// 如果不是后退操作,且当前指针小于数组总长度,直接截取指针之前的部分储存下来
// 此操作来避免当点击后退按钮之后,再进行正常跳转,指针会停留在原地,而数组添加新hash路由
// 避免再次造成指针的不匹配,我们直接截取指针之前的数组
// 此操作同时与浏览器自带后退功能的行为保持一致
if (this.currentIndex < this.history.length - 1)
this.history = this.history.slice(0, this.currentIndex + 1);
this.history.push(this.currentUrl);
this.currentIndex++;
}
this.routes[this.currentUrl]();
console.log('指针:', this.currentIndex, 'history:', this.history);
this.isBack = false;
}
// 后退功能
backOff() {
// 后退操作设置为true
this.isBack = true;
// 如果指针小于0的话就不存在对应hash路由了,因此锁定指针为0即可
this.currentIndex <= 0 ? (this.currentIndex = 0) : (this.currentIndex = this.currentIndex - 1);
// 随着后退,location.hash也应该随之变化
location.hash = `#${this.history[this.currentIndex]}`;
// 执行指针目前指向hash路由对应的callback
this.routes[this.history[this.currentIndex]]();
}
}

一个简单的 HistoryAPI

class Routers {
constructor() {
this.routes = {};
// 在初始化时监听popstate事件
this._bindPopState();
}
// 初始化路由
init(path) {
history.replaceState({ path: path }, null, path);
this.routes[path] && this.routes[path]();
}
// 将路径和对应回调函数加入hashMap储存
route(path, callback) {
this.routes[path] = callback || function() {};
}

// 触发路由对应回调
go(path) {
history.pushState({ path: path }, null, path);
this.routes[path] && this.routes[path]();
}
// 监听popstate事件
_bindPopState() {
window.addEventListener('popstate', e => {
const path = e.state && e.state.path;
this.routes[path] && this.routes[path]();
});
}
}

比较起来, 后者的实现更简单一些. 具体更详细的 History API,看这里