前端路由是现代单页应用(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 同样提供了 HashRouter 和 BrowserRouter 两种组件,对应 hash 和 history 模式。在日常开发中,根据项目对 SEO 和 URL 美观度的要求来选择即可,实现原理则始终围绕上述的基础 API。