前端路由需依赖history.pushState和popstate/hashchange事件实现无刷新导航,避免仅监听click导致前进/后退失效、刷新404及SEO问题;必须与浏览器历史栈联动并处理首次加载、重复绑定等细节。

前端路由不需要服务端参与就能切换页面视图,核心靠的是 history.pushState 和 hashchange 事件 —— 但直接手写一个“可用”的路由远比想象中容易出错。
为什么不能只监听 click 事件跳转?
单纯给所有 a 标签加 preventDefault() 再手动更新 DOM,会丢失浏览器原生能力:前进/后退按钮失效、刷新页面 404、SEO 友好性归零。真正的前端路由必须和浏览器历史栈联动。
-
history.pushState()不触发页面刷新,但会把新状态推入历史栈,同时改变地址栏 URL(注意:不会发送 HTTP 请求) -
popstate事件在用户点击前进/后退按钮时触发,是响应式更新视图的唯一可靠时机 -
hashchange更轻量,兼容性更好(IE8+),但 URL 中带#,对 SEO 和部分后端代理不友好
history.pushState 的三个参数到底怎么填?
调用格式是 history.pushState(state, title, url),其中:
-
state是任意可序列化的 JS 值(如{page: 'user', id: 123}),它会被存入历史记录,后续在popstate事件中通过event.state取回 —— 这是传递路由参数最干净的方式,别硬塞到 URL 查询字符串里 -
title目前所有主流浏览器都忽略它,传空字符串''即可 -
url必须是同源的相对路径(如/user/123),不能跨域,也不能是完整 URL(如https://xxx.com/user),否则报错SecurityError
如何避免 popstate 事件重复触发或漏触发?
常见陷阱是:在单页应用中,popstate 可能被多次绑定(比如组件反复挂载)、也可能在首次加载时没触发(因为初始页面不是由 pushState 进来的)。
立即学习“Java免费学习笔记(深入)”;
- 务必使用
addEventListener('popstate', handler, { passive: true }),且只绑定一次(例如在应用初始化时) - 首次加载需主动匹配当前 URL 并渲染对应视图,不能等
popstate—— 它只响应“导航动作”,不响应“初始加载” - 如果用了
replaceState(比如登录后替换掉 /login 路径),它不会触发popstate,所以状态同步逻辑要独立处理
const router = {
routes: new Map(),
init() {
// 首次加载:解析当前 location.pathname
this.render(location.pathname);
// 绑定 popstate
addEventListener('popstate', (e) => {
this.render(e.state?.path || location.pathname);
});
},
go(path) {
history.pushState({ path }, '', path);
this.render(path);
},
render(path) {
const Component = this.routes.get(path) || NotFound;
document.getElementById('app').innerHTML = Component();
}
};
Hash 模式下 location.hash 的值带不带 #?
带。读取时 location.hash 返回的是完整字符串,如 #/user/123;但 pushState 不适用 hash 模式,你得用 location.hash = '/user/123'(注意这里不用写 #,浏览器会自动补上)。
- 监听变化要用
addEventListener('hashchange', ...),它的触发时机和popstate类似,但更早兼容 - hashchange 回调里,
event.oldURL和event.newURL是完整 URL 字符串,解析推荐用location.hash.slice(1) - 不要混用 hash 和 history 模式:比如在 history 模式下手动改
location.hash,会导致 URL 变成/user#abc,既难维护又破坏语义
真正难的不是实现跳转,而是让路由状态和组件生命周期、数据请求、滚动位置、嵌套路由、动态加载全部对齐 —— 手写路由很快会变成状态泥潭。除非你明确知道哪些功能可以砍掉,否则建议直接用 react-router 或 vue-router 的最小运行时版本。











