前端路由的三种实现方式:Hash、History 与 Memory

为什么前端需要路由

在传统多页应用中,每次页面跳转都由服务器返回一份全新的 HTML,浏览器负责重新渲染。而单页应用(SPA)把这一职责转移到了客户端:页面只加载一次,后续的“跳转”由 JavaScript 拦截并动态替换视图内容。前端路由就是这套机制的核心,它决定了 URL 如何变化、视图如何映射,以及浏览器前进/后退按钮是否仍然可用。

不同的运行环境对路由提出了不同要求。浏览器中有地址栏和 History API,可以用 Hash 或 History 模式;而在非浏览器环境(如 React Native、测试环境、Electron 主进程)中,根本没有 URL 地址栏,就需要一种纯内存的路由方案。下面逐一拆解这三种实现方式。

Hash 模式:最经典也最兼容

Hash 模式利用 URL 中 # 之后的部分(即 hash)来标记路由状态。例如 https://example.com/#/user/1 中的 #/user/1 就是路由信息。

它的核心优势来自一个浏览器特性:hash 的变化不会触发页面刷新,也不会向服务器发送请求。这意味着无论 hash 怎么变,服务器始终只返回同一个 HTML 文件,剩下的交给前端处理。

实现上主要依赖两个事件:

  • window.location.hash 读取当前 hash
  • hashchange 事件监听 hash 变化

一个极简实现如下:

class HashRouter {
  constructor() {
    this.routes = {};
    window.addEventListener('hashchange', () => this.load());
    window.addEventListener('load', () => this.load());
  }

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

  load() {
    const path = window.location.hash.slice(1) || '/';
    const handler = this.routes[path] || this.routes['/404'];
    handler && handler();
  }

  push(path) {
    window.location.hash = path;
  }
}

Hash 模式的优点很明确:兼容性极好,从 IE8 到现代浏览器都能工作;不需要服务器做任何配置,部署到任意静态服务器都不会出现 404。缺点是 URL 中始终带着 #,视觉上不够美观,且 hash 部分不会被发送到服务器,对 SEO 不友好(虽然现代搜索引擎已能抓取 hash 内容,但效果仍不如普通路径)。

History 模式:更优雅的 URL

History 模式基于 HTML5 新增的 History API,使用普通的路径形式,如 https://example.com/user/1。它看起来和传统 URL 完全一致,没有多余的符号,因此更美观,也更利于 SEO。

核心 API 有三个:

  • history.pushState(state, title, url):向历史栈压入一条新记录,不刷新页面
  • history.replaceState(state, title, url):替换当前记录,不新增
  • popstate 事件:在用户点击前进/后退按钮时触发

实现思路与 Hash 类似,但监听的事件换成了 popstate

class HistoryRouter {
  constructor() {
    this.routes = {};
    window.addEventListener('popstate', () => this.load());
    window.addEventListener('load', () => this.load());
  }

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

  load() {
    const path = window.location.pathname;
    const handler = this.routes[path] || this.routes['/404'];
    handler && handler();
  }

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

这里有一个容易踩的坑:调用 pushState 本身不会触发 popstate,所以需要手动调用一次 load() 来更新视图。popstate 只在用户主动前进/后退时触发。

History 模式最大的代价在服务端。因为 URL 是普通路径,用户直接在地址栏输入 https://example.com/user/1 或刷新页面时,浏览器会真的向服务器请求 /user/1。如果服务器没有对应资源,就会返回 404。解决办法是在服务器配置一个 fallback,把所有未匹配的请求都重定向到 index.html。以 Nginx 为例:

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

Vue Router 和 React Router 的 history 模式都需要这一步配置,否则线上刷新必崩。

Memory 模式:脱离 URL 的路由

Memory 模式(也叫 Abstract 模式)不在地址栏留下任何痕迹,路由状态完全保存在内存中的一个变量里。它不依赖 windowlocationhistory,因此可以在任何 JavaScript 环境中运行。

class MemoryRouter {
  constructor(initialPath = '/') {
    this.routes = {};
    this.current = initialPath;
    this.listeners = [];
  }

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

  push(path) {
    this.current = path;
    this.notify();
  }

  replace(path) {
    this.current = path;
    this.notify();
  }

  notify() {
    const handler = this.routes[this.current] || this.routes['/404'];
    handler && handler();
    this.listeners.forEach(fn => fn(this.current));
  }

  subscribe(fn) {
    this.listeners.push(fn);
  }
}

Memory 模式的典型应用场景包括:

  • 服务端渲染(SSR):在 Node.js 中为每个请求创建独立的路由实例,避免请求之间状态互相污染
  • React Native / 小程序:没有浏览器 URL 概念,但依然需要页面栈管理
  • 单元测试:不依赖真实浏览器环境,测试更稳定
  • Electron 主进程或某些嵌入式 WebView:URL 不可控或不需要暴露

它的缺点也很明显:用户无法通过 URL 直接分享或收藏某个页面,浏览器前进/后退按钮失效,刷新页面会回到初始状态。因此它通常只用于特定环境,而不是普通 Web 应用的首选。

三种方式的对比与选型

维度 Hash History Memory
URL 形式 /#/path /path
页面刷新 不丢失 需服务端配置 丢失
服务端配置 不需要 需要 fallback 不需要
SEO 较弱 不适用
兼容性 极好 IE10+ 与环境无关
典型场景 静态部署、旧项目 现代 Web 应用 SSR、RN、测试

选型时可以参考几条简单原则:如果部署环境无法修改服务器配置(比如托管在 GitHub Pages 或纯静态 CDN),Hash 模式最省心;如果追求干净的 URL 和更好的 SEO,并且能控制服务器,History 模式是首选;如果代码需要跑在浏览器之外,或者需要为每个请求隔离路由状态,就用 Memory 模式。

值得一提的是,Vue Router 和 React Router 都同时支持这三种模式,API 设计高度一致,切换模式通常只需要改一行创建路由实例的代码。理解它们底层的差异,能帮助你在遇到刷新 404、SSR 状态污染等问题时快速定位原因,而不是停留在“换个模式试试”的层面。

未经允许不得转载:任鹏个人博客 » 前端路由的三种实现方式:Hash、History 与 Memory

赞 (0) 打赏

评论 0

取消
  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏