前端路由的 hash 模式和 history 模式有什么区别?
一句话回答
前端路由的本质是改变 URL 但不向服务器请求新页面,再根据 URL 渲染对应的组件。hash 模式利用 URL 中 # 后面的部分,它变化时不发请求,通过 hashchange 事件监听,不需要服务端配置;history 模式用 history.pushState / replaceState 修改路径,通过 popstate 监听前进后退,URL 更美观,但需要服务端把所有前端路由都回退到 index.html,否则刷新页面会 404。
详细解析
hash 模式
- URL 形如
https://example.com/#/user/1,#后面的部分只在浏览器端使用,不会发送给服务器 - 修改 hash(
location.hash = '/user/1',或点击href="#/user/1"的链接)不会刷新页面,会新增一条历史记录,并触发hashchange事件 - 优点:兼容性好,不需要服务端配置
- 缺点:URL 带
#不美观;#被路由占用后,不能直接用它做页面内的锚点跳转;服务器拿不到#后面的路径,没法做服务端渲染
history 模式
- URL 形如
https://example.com/user/1 history.pushState(state, '', url)新增一条历史记录,replaceState替换当前记录,两者都不刷新页面(第二个参数已废弃,传空字符串即可)popstate在浏览器前进、后退,或调用history.back()/go()时触发(修改 hash 也会触发),调用pushState、replaceState本身不会触发。所以路由库要拦截站内链接的点击,调用pushState之后自己更新视图pushState只能修改为同源的 URL,否则会抛出错误
服务端配置
用户刷新页面或直接访问 /user/1 时,浏览器会真的向服务器请求这个路径,服务器上没有对应的文件就返回 404。需要配置"找不到文件就返回 index.html",交给前端路由处理,例如 Nginx:
location / {
try_files $uri $uri/ /index.html;
}
这样一来,不存在的路径也会返回 200,真正的 404 页面要由前端路由的兜底路由渲染。
对比
| hash 模式 | history 模式 | |
|---|---|---|
| URL 形式 | /#/user/1 |
/user/1 |
| 修改 URL | 修改 location.hash |
pushState / replaceState |
| 监听变化 | hashchange |
popstate(前进后退时触发,pushState 不触发) |
| 发给服务器的路径 | 只有 # 前面的部分 |
完整路径 |
| 服务端配置 | 不需要 | 需要回退到 index.html |
| SEO 和 SSR | 差 | 可以配合 SSR |
| Vue Router | createWebHashHistory() |
createWebHistory() |
Vue Router 的导航守卫和完整导航流程见导航守卫。较新的 Navigation API 提供了 navigate 事件,可以统一拦截链接点击、表单提交、前进后退等各种导航,2026 年起主流浏览器的新版本都已支持,老版本还需要兼容。
代码示例
// 路由表:路径 → 渲染函数,'*' 兜底
const routes = {
'/': () => '<h1>首页</h1>',
'/about': () => '<h1>关于</h1>',
'*': () => '<h1>404</h1>',
}
const app = document.querySelector('#app')
// 1. hash 路由
function createHashRouter() {
function render() {
const path = location.hash.slice(1) || '/' // '#/about' → '/about'
app.innerHTML = (routes[path] || routes['*'])()
}
window.addEventListener('hashchange', render) // 修改 hash、前进后退都会触发
render() // 首次进入页面时渲染
return {
push(path) {
location.hash = path // 会触发 hashchange,不用手动渲染
},
}
}
// 2. history 路由
function createHistoryRouter() {
function render() {
app.innerHTML = (routes[location.pathname] || routes['*'])()
}
function push(url) {
history.pushState(null, '', url) // 只改 URL,不会触发 popstate
render() // 所以要自己更新视图
}
window.addEventListener('popstate', render) // 前进、后退时触发
// 用事件委托拦截站内链接的点击
document.addEventListener('click', (e) => {
const a = e.target.closest('a')
if (!a || a.origin !== location.origin || a.target === '_blank') return
if (e.ctrlKey || e.metaKey || e.shiftKey || e.altKey) return // 保留新标签页打开等默认行为
e.preventDefault()
push(a.href)
})
render()
return { push }
}
面试官可能追问
为什么 history 模式下刷新页面会 404?
站内跳转时,URL 是 pushState 改的,浏览器并没有请求服务器。刷新或直接打开 /user/1 时,浏览器会真的去请求这个路径,而服务器上只有 index.html 和静态资源,没有 /user/1 这个文件,就返回 404。解决办法是服务端配置回退:找不到文件时返回 index.html,再由前端路由根据路径渲染。
hash 模式对 SEO 有什么影响?
# 后面的内容不会发给服务器,对服务器来说所有页面都是同一个 URL,没法针对不同路由做服务端渲染,返回不同的标题和描述。搜索引擎一般也会忽略 URL 中 # 后面的部分,容易把它们当成同一个页面。需要 SEO 的站点应该用 history 模式,配合服务端渲染或预渲染。
history 模式下怎样拦截所有站内链接的点击?
在 document 上监听 click,利用事件委托找到被点击的 <a>。跳过外部链接、target="_blank" 的链接,以及按住 Ctrl、Cmd、Shift 的点击(用户想在新标签页或新窗口打开),其余的调用 preventDefault() 阻止整页跳转,再 pushState 并渲染。Vue Router 不会拦截普通的 <a>,它的 <RouterLink> 在点击时做了这些判断,再调用 router.push。
易错点
pushState/replaceState不会触发popstate,前进、后退时才会触发- history 模式必须配置服务端回退,否则刷新 404;配置回退后,不存在的路径也返回 200,要由前端渲染 404 页面
- hash 模式下
#后面的内容不会发送给服务器
AI 模拟面试官
用自己的话回答,AI 对照参考答案打分、指出遗漏,再追问,最多 3 轮
这道题你掌握了吗?
选一个最接近的状态,没掌握的题会出现在"我的进度 · 待复习"里。
学习记录暂存在本机浏览器。登录后自动同步到账号,换设备也能看到。