hash 路由和 history 路由的原理是什么?服务器要怎样配置回退?
hash 路由把路径放在
参考答案
简答
- hash 路由:地址形如
/#/user/1。#后面的片段不会随请求发给服务器,修改location.hash不会刷新页面,值变化时触发hashchange事件。 - history 路由:
history.pushState(state, '', url)修改地址栏并新增历史记录,浏览器不会加载该地址,也不会触发popstate,路由库要在调用后自己渲染新页面。 popstate在用户前进、后退等切换历史记录时触发,路由库监听它来同步界面。pushState的地址必须与当前页面同源。- history 模式下刷新或直接打开
/user/1时,浏览器会真正请求这个路径。静态服务器上没有对应文件,需要配置为返回index.html,由前端路由处理。
路由要管理的数据、深链接和权限边界见《前端路由除切换组件外还需要管理什么?》,本题只讲两种模式怎样手写,以及服务器怎样配置回退。
hash 路由
JavaScript
function renderByHash() {
const path = location.hash.slice(1) || '/';
render(path);
}
window.addEventListener('hashchange', renderByHash);
renderByHash();
// 跳转
location.hash = '/user/1';
给 location.hash 赋与当前相同的值不会触发 hashchange。hash 模式不依赖服务器配置,代价是地址带 #,并且与页内锚点共用片段。
history 路由
JavaScript
function navigate(url) {
history.pushState(null, '', url);
render(location.pathname); // pushState 不会触发 popstate,要手动渲染
}
window.addEventListener('popstate', () => render(location.pathname));
document.addEventListener('click', (event) => {
const link = event.target.closest('a[href^="/"]');
// 保留新标签页、下载和其他脚本已处理的点击
if (
!link ||
event.defaultPrevented ||
event.button !== 0 ||
event.metaKey ||
event.ctrlKey ||
event.shiftKey ||
event.altKey ||
link.target ||
link.hasAttribute('download')
)
return;
event.preventDefault();
navigate(link.getAttribute('href'));
});
replaceState 参数相同,只是替换当前记录,适合规范化查询参数这类不应产生新历史的场景。
服务器回退
浏览器调用 pushState 时不请求新地址,但之后刷新、复制链接打开或重启浏览器恢复标签页时都会请求它。服务器必须能响应所有前端路由路径。
Nginx 的常见写法是先找真实文件,找不到再返回入口页:
nginx
location / {
try_files $uri $uri/ /index.html;
}
回退之后服务器对任何未知路径都返回 200 和入口页,真正不存在的页面要由前端渲染 404 界面。静态资源路径写错时也可能拿到 HTML 而不是脚本,排查时注意响应类型。
部署在子路径(例如 /app/)时,路由库的 base 和构建工具的资源前缀都要设成同一个子路径,回退目标也要是该子路径下的 index.html。不支持回退配置的托管平台上,可以改用 hash 模式。
常见追问
- 为什么 history 模式对 SEO 更友好?每个页面有独立的真实路径,服务端渲染或预渲染时可以按路径输出不同内容;hash 片段不发给服务器,无法据此返回不同页面。
pushState的第二个参数有用吗?它是历史遗留的标题参数,浏览器基本忽略,通常传空字符串。