前端路由hash & history

前端路由hash & history


路由 hash history 前端

hash 模式

hash模式是基于location.hash来实现的,通过onhashchange事件监听 hash 改变,从而对页面进行跳转(渲染) 比如下面这个网站,它的 location.hash 的值为 #home

https://www.xiejunyi.com#home

特点

  • URL 中的 hash 值只是客户端的一种状态,服务器发出请求时,hash 部分不会被发送
  • hash 值的改变,都会在浏览器历史中增加一个记录,因此我们可以通过浏览器的前进、后退控制 hash 的切换
  • 可以通过 onhashchange 事件来监听 hash 的变化,render 页面

history 模式

history 模式通过 H5 中History API来实现 url 的变化。主要通过 pushState()replaceState()这两个 api 结合popstate事件实现页面跳转(渲染) history 模式与原 url 无异,不带#字符,比如下面这个网站

https://www.xiejunyi.com/home

特点

  • pushStaterepalceState 两个 API 来操作实现当前 URL 的变化
  • 通过popstate事件来监听 URL 变化(点击浏览器前进、后退等操作时)
  • pushStatereplaceState不会触发popstate事件,需要手动触发页面跳转(渲染)

区别:

  • hash 模式,#后的信息不会包含在 http 请求中,history 需要后端(nginx)进行适配,否则刷新会访问真实 url
  • 兼容性:hash兼容 IE8 以上、history兼容 IE10 以上
  • 描述性:hash只能添加字符串,history可以添加title、state等描述对象
  • 局限性:hash只能改变#后的字符串,history模式可以修改同源的任意 URL

手写实现:

hash 模式

class Router {
  constructor() {
    this.routers = []; //存放路由配置
  }
  add(route, callback) {
    this.routers.push({
      path: route,
      render: callback,
    });
  }
  listen(callback) {
    window.onhashchange = this.hashChange(callback);
    this.hashChange(callback)(); //首次进入页面的时候没有触发hashchange,要单独调用一下
  }
  hashChange(callback) {
    let self = this;
    return function () {
      let hash = location.hash;
      for (let i = 0; i < self.routers.length; i++) {
        let route = self.routers[i];
        if (hash === route.path) {
          callback(route.render());
          return;
        }
      }
    };
  }
}
let router = new Router();
router.add("#index", () => {
  return "<h1>这是首页内容</h1>";
});
router.add("#article", () => {
  return "<h1>这是文章内容</h1>";
});
router.add("#user", () => {
  return "<h1>这是个人内容</h1>";
});
router.listen((renderHtml) => {
  let app = document.getElementById("app");
  app.innerHTML = renderHtml;
});

history 模式

class Router {
  constructor() {
    this.routers = [];
    this.renderCallback = null;
  }
  add(route, callback) {
    this.routers.push({
      path: route,
      render: callback,
    });
  }
  pushState(path, data = {}) {
    window.history.pushState(data, "", path);
    this.renderHtml(path);
  }
  listen(callback) {
    this.renderCallback = callback;
    this.changeA();
    window.onpopstate = () => this.renderHtml(this.getCurrentPath());
    this.renderHtml(this.getCurrentPath());
  }
  changeA() {
    document.addEventListener("click", (e) => {
      if (e.target.nodeName === "A") {
        e.preventDefault();
        let path = e.target.getAttribute("href");
        this.pushState(path);
      }
    });
  }
  getCurrentPath() {
    return location.pathname;
  }
  renderHtml(path) {
    for (let i = 0; i < this.routers.length; i++) {
      let route = this.routers[i];
      if (path === route.path) {
        this.renderCallback(route.render());
        return;
      }
    }
  }
}

let router = new Router();
router.add("/index", () => {
  return "<h1>这是首页内容</h1>";
});
router.add("/article", () => {
  return "<h1>这是文章内容</h1>";
});
router.add("/user", () => {
  return "<h1>这是个人内容</h1>";
});
router.listen((renderHtml) => {
  let app = document.getElementById("app");
  app.innerHTML = renderHtml;
});
© 2025 Niko Xie