为什么前端需要路由
在传统多页应用中,每次页面跳转都由服务器返回一份全新的 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读取当前 hashhashchange事件监听 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 模式)不在地址栏留下任何痕迹,路由状态完全保存在内存中的一个变量里。它不依赖 window、location 或 history,因此可以在任何 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


朋友圈点赞图在线生成源码