使用 history.pushState 可实现无刷新导航,需配合 AJAX 加载内容、阻止链接默认行为、监听 popstate 更新视图,并配置服务端 fallback 支持所有路由返回同一 HTML。

点击导航栏不刷新页面,用 history.pushState 替换 URL
HTML5 本身不“切换页面”,它靠 JavaScript 操作浏览器历史栈实现无刷新跳转。核心是 history.pushState —— 它改地址栏、加记录,但不触发页面重载。
常见错误现象:点了导航链接,URL 变了但内容没更新,或直接整页刷新了;或者用了 pushState 却没手动加载新内容,页面卡在旧状态。
- 必须配合 AJAX 或动态 DOM 替换(比如
fetch加载 HTML 片段后innerHTML注入) -
pushState第一个参数是任意 state 对象(可存页面标识),第二个是标题(多数浏览器忽略),第三个才是关键的url(需同源,且最好为相对路径如/about) - 不处理
popstate事件的话,用户点浏览器「后退」按钮会卡住——URL 变了但视图没响应
导航链接必须阻止默认行为,否则照样刷新
写个 联系我 是没用的,点击瞬间浏览器就发起 GET 请求并全量重载。得用 JS 截住它。
使用场景:所有单页应用(SPA)式导航,包括手写轻量路由或基于框架的简化版。
立即学习“前端免费学习笔记(深入)”;
- 给每个导航
绑定click事件,开头就写event.preventDefault() - 别只靠
return false,它在现代事件监听器里无效;必须显式调用preventDefault - 如果用事件委托(比如监听
nav容器),注意event.target可能是内部或图标,要用closest('a')定位链接
服务端要支持“所有路由返回同一份 HTML”
前端用 pushState 跳到 /dashboard 没问题,但用户直接在地址栏输这个 URL 回车,就会 404——因为服务器真没这个路径。
性能 / 兼容性影响:这是纯服务端配置问题,和前端代码无关;不配好,SEO 和分享链接都失效。
- 开发时用 Vite / Webpack Dev Server,开
historyApiFallback: true - Nginx 配置加一行:
try_files $uri $uri/ /index.html; - Cloudflare Pages、Vercel、Netlify 默认已支持,无需额外操作
popstate 事件里不能只改 URL,得同步更新视图
用户按浏览器后退键时,popstate 触发,但此时页面还是上一个状态的内容。很多人只在这里读取 event.state,忘了重新渲染。
容易踩的坑:把 popstate 监听写在某个函数里,但该函数只执行一次;或监听了却没判断当前 URL 和目标 URL 是否真不同,导致重复加载。
- 监听必须在全局(如
window.addEventListener('popstate', handler)),且最好在页面初始化时就注册 - handler 里应根据
location.pathname(而非event.state)决定加载哪块内容——因为state可能为空(比如从外站跳进来) - 避免两次加载相同路径:先比对
location.pathname和当前已显示的模块名,不同时才 fetch + render










