人人都会AI编程

31.6 前端路由:hash 模式与 history 模式实现

更新时间:2026-07-11

前端路由是现代单页应用(SPA)的基石,它让页面在不需要重新加载的情况下,根据 URL 的变化切换显示不同的内容。实现前端路由的两种主流方案分别是 hash 模式和 history 模式,它们的核心区别在于如何利用浏览器提供的 API 来管理 URL 和监听变化。

hash 模式

原理

URL 中以 # 开头(包括 # 之后的部分)叫做哈希值(hash),其原本用途是用作页面内的锚点定位。关键特性是:哈希值的变化不会触发页面的完整刷新

通过监听浏览器的 hashchange 事件,我们可以在哈希变化时执行对应的逻辑(比如渲染指定组件),而 window.location.hash 可以直接读写当前哈希。

基础实现示例

class HashRouter {
  constructor() {
    this.routes = {};          // 存储路径与回调的映射
    this.currentUrl = '';      // 当前路径(不含 #)

    // 监听 hashchange 事件
    window.addEventListener('hashchange', () => this.onHashChange());
    // 页面首次加载时手动触发一次
    window.addEventListener('load', () => this.onHashChange());
  }

  // 注册路由
  route(path, callback) {
    this.routes[path] = callback;
  }

  // 当哈希变化时匹配路由并执行回调
  onHashChange() {
    // 获取 hash 部分并去掉开头的 #
    this.currentUrl = window.location.hash.slice(1) || '/';
    const route = this.routes[this.currentUrl];
    if (route) {
      route();
    } else {
      console.warn(`路由 ${this.currentUrl} 未注册`);
    }
  }
}

// 使用示例
const router = new HashRouter();
router.route('/', () => { console.log('首页'); });
router.route('/about', () => { console.log('关于页'); });
router.route('/user/1', () => { console.log('用户详情'); });

手动改变路由可以这样:<a href="#/about">关于</a>,或者通过 window.location.hash = '/about'

特点与注意事项

  • 兼容性极好,所有浏览器均支持。
  • URL 中带 #,看起来不够“干净”,但对于不需要 SEO 或后端配置简单的项目完全够用。
  • 服务端永远只能收到 # 前面的路径,所以不会出现 404,部署最简单。

history 模式

原理

HTML5 引入了 history.pushState()history.replaceState() 两个 API,它们可以在不刷新页面的前提下修改浏览器的地址栏 URL。配合 popstate 事件,就可以监听浏览器的前进/后退操作,从而实现完全自定义的路由逻辑。

  • history.pushState(state, title, url) – 新增一条历史记录并将地址栏改为指定 URL。
  • history.replaceState(state, title, url) – 替换当前历史记录,不新增。
  • window.addEventListener('popstate', callback) – 当用户点击前进/后退按钮或调用 history.go() 时触发,但 pushState / replaceState 不会触发该事件

基础实现示例

class HistoryRouter {
  constructor() {
    this.routes = {};
    // 阻止对链接的默认行为,改为 pushState
    document.addEventListener('click', (e) => {
      const el = e.target.closest('a');
      if (el && el.href.startsWith(window.location.origin)) {
        e.preventDefault();
        const path = el.getAttribute('href');
        this.push(path);
      }
    });
    // 监听前进/后退
    window.addEventListener('popstate', () => this.onPopState());
    // 首次加载匹配当前路径
    window.addEventListener('load', () => this.onPopState());
  }

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

  push(path) {
    history.pushState(null, '', path);
    this.renderRoute(path);
  }

  replace(path) {
    history.replaceState(null, '', path);
    this.renderRoute(path);
  }

  onPopState() {
    this.renderRoute(window.location.pathname);
  }

  renderRoute(path) {
    const route = this.routes[path] || this.routes['*']; // 支持通配符
    if (route) {
      route();
    } else {
      console.warn(`路由 ${path} 未找到`);
    }
  }
}

// 使用示例
const router = new HistoryRouter();
router.route('/', () => { console.log('首页'); });
router.route('/about', () => { console.log('关于页'); });

必须注意的服务器配置问题

history 模式下,URL 是“真实”的路径(如 /about),当用户直接访问该地址或刷新页面时,浏览器会向服务器发送请求。如果服务器没有对单页应用的入口文件(通常是 index.html)进行兜底配置,就会返回 404。因此需要在服务器端添加配置:

Nginx 示例

location / {
    try_files $uri $uri/ /index.html;
}

Node.js Express 示例

const express = require('express');
const path = require('path');
const app = express();
app.use(express.static('dist'));
app.get('*', (req, res) => {
    res.sendFile(path.resolve(__dirname, 'dist', 'index.html'));
});

这样所有未命中静态资源的请求都会落到 index.html 上,由前端路由接管。

hash 与 history 对比总结

| 特性 | hash 模式 | history 模式 |
| ------------ | --------------------------------------- | ------------------------------------ |
| URL 外观 | 带 #,如 example.com/#/user | 正常路径,如 example.com/user |
| 实现关键 API | hashchange 事件 | pushState + popstate 事件 |
| 兼容性 | 所有浏览器完美支持 | IE10 及以上,现代浏览器均支持 |
| 服务端依赖 | 不需要特殊配置,哈希部分不会发给服务器 | 必须配置兜底规则,否则刷新会 404 |
| SEO 友好性 | 较差,搜索引擎通常忽略哈希后面的内容 | 较好(需配合 SSR 或预渲染) |
| 典型场景 | 简单应用、后台管理系统、无需 SEO 的项目 | 面向用户的前台项目,需要干净 URL |

工程化与框架中的路由

在现代前端框架中,路由库会同时支持两种模式。以 Vue Router 为例,创建 router 时选择:

// hash 模式
const router = createRouter({
    history: createWebHashHistory(),
    routes: [...]
});

// history 模式
const router = createRouter({
    history: createWebHistory(),
    routes: [...]
});

React 的 React Router 同样提供了 HashRouterBrowserRouter 两种组件,对应 hash 和 history 模式。在日常开发中,根据项目对 SEO 和 URL 美观度的要求来选择即可,实现原理则始终围绕上述的基础 API。