"咦?我点了个'关于我们',浏览器地址栏变了,但页面居然没刷新?!" 如果你也有过类似疑问,恭喜你,今天我们来揭开前端路由中最神秘的"障眼法"------SPA 路由的历史魔法(
history API
) !
一切的起源:古老的 history
对象
早在 HTML5 之前,浏览器里就有个叫 window.history
的家伙,功能嘛......很单纯:
scss
history.back(); // 回退
history.forward(); // 前进
history.go(-1); // 后退一页
这就像浏览器的"时光机",你可以在历史中前后穿梭。但仅此而已,它不能 篡改地址,也不能让你"伪装"页面。
hash 路由:前端第一代"穿墙术"
然后前端老祖宗想出了个招儿:用 #
来假装路径变了!
ruby
// 网址长这样
https://example.com/#/about
并监听这个事件:
javascript
window.addEventListener('hashchange', () => {
console.log(location.hash); // #/about
});
只要 #
后面变了,前端就换个组件,页面不刷新。听起来很牛?但 hash 有 几个尴尬的问题:
- URL 丑:
https://xxx.com/#/about
,像不像锚点导航? - SEO 不友好:爬虫不看
#
后面的 - 用户体验差:刷新页面,hash 前面全都跑后端入口去了
HTML5 的历史大跃进:pushState & replaceState
到了 HTML5,历史 API 终于升级了,给你直接"改地址"的权力,但页面不刷新!
lua
history.pushState({path: '/about'}, '', '/about');
- ✅ 地址栏变成
/about
- ✅ 页面没刷新
- ✅ 可以监听返回按钮
这就是现代 SPA(单页面应用)路由的核心机制!
来点实战:自己造个 SPA 路由
我们用最原始的 HTML+JS,手撸一个模拟 Vue/React 的路由系统
html
<body>
<h2>SPA路由模拟</h2>
<button onclick="navigate('/home')">首页</button>
<button onclick="navigate('/about')">关于</button>
<button onclick="navigate('/contact')">联系</button>
<button onclick="replace('/pay')">支付</button>
<div id="view">当前视图</div>
<script>
function render(path) {
document.getElementById('view').innerHTML = `当前视图: ${path}`;
}
function navigate(path) {
history.pushState({ path }, '', path);
render(path);
}
function replace(path) {
history.replaceState({ path }, '', path);
render(path);
}
window.addEventListener('popstate', (event) => {
render(event.state?.path || location.pathname);
});
</script>
</body>
一步步解释下:
-
按钮点击 调用
navigate('/about')
:-
当你点击页面上的"关于"按钮时,浏览器会执行
navigate('/about')
这个函数。 -
这个函数内部调用了
history.pushState({ path: '/about' }, '', '/about')
,意思是:- 给浏览器历史记录里"添加一条新记录" ,这条记录的地址是
/about
。 - 地址栏的 URL 变成了
/about
,但浏览器页面并没有刷新(不会重新加载页面资源)。
- 给浏览器历史记录里"添加一条新记录" ,这条记录的地址是
-
接着,函数调用了
render('/about')
,它会把页面中显示内容的那个区域(id 是view
的div
)更新成"当前视图:/about",就像你"跳转"到"关于"页面了。
-
-
点击浏览器返回按钮 触发
popstate
:- 当你点击浏览器的"后退"按钮时,浏览器会触发
popstate
事件。 - 事件回调里的代码会读取当前历史记录的状态(即
event.state
),如果有path
,就用这个路径重新调用render()
函数。 - 这样页面的显示内容会变回你之前访问过的那个页面,用户就感觉"真的回去了",虽然整个页面没有刷新。
- 这个过程保证了用户用浏览器的前进后退按钮,也能正确看到对应的内容。
- 当你点击浏览器的"后退"按钮时,浏览器会触发
-
replaceState
vspushState
:pushState
是 往浏览器历史记录栈中添加一条新记录,意味着你每次调用它,浏览器就像多加了一页历史,用户可以点击"后退"按钮逐条回去。replaceState
则是 替换当前这条历史记录,不会新增,也就是说"历史记录不变长",用户按"返回"不会回到之前的那个地址。- 比如你登录成功后跳到支付页面,如果用
pushState
,用户点"返回"会回到登录页;用replaceState
,就直接把登录页那条记录给替换掉了,防止用户回去重复登录。
这样讲解,既保留了你的步骤清晰,又加了细节和易懂的比喻。你觉得怎么样?需要我帮你做成掘金文章的小节格式吗?
整个套路像不像魔术?
- 地址栏变化,浏览器却不刷新页面
- 用户以为这是多页面,其实都是同一个 HTML 文件
- 全靠前端维护"假路由"+ 页面渲染
这,就是现代前端的开发
✅ 总结:一图秒懂,前端路由技术大比拼
技术 | 地址栏会变吗? | 页面会刷新吗? | 能否监听变化事件? | SEO 友好吗? |
---|---|---|---|---|
hash 路由 | ✅ | ❌ | ✅(监听 hashchange ) |
❌,搜索引擎爬虫不友好 |
history.pushState | ✅ | ❌ | ✅(监听 popstate ) |
✅(配合服务端渲染SSR) |
细节补充:
- hash 路由 :基于 URL 的
#
符号实现,兼容性极佳,但 URL 不够美观且对 SEO 支持有限。- history API :通过
pushState
和replaceState
修改浏览器地址栏,实现真正意义上的"无刷新路由切换",也方便后续和后端进行统一路由管理,SEO 友好。
最后聊聊"前端路由的秘密武器"------history.pushState
虽然这个 API 看起来简单,但它却是现代前端框架(React Router、Vue Router、Angular Router 等)坚实的基石。它让网页看起来像传统多页应用一样流畅切换,实际上却是单页面应用在背后"玩魔法":
- 地址栏地址变了,用户感觉跳转了新页面
- 但其实页面没有刷新,用户体验大幅提升
- 浏览器返回前进按钮依然可用,历史管理有条理
- SEO 通过服务端渲染(SSR)配合,可实现友好索引
你的收获
现在,你不仅能轻松写出"点按钮不刷新页面"的 SPA 路由,还知道:
- 浏览器历史记录到底是什么鬼
pushState
和replaceState
有什么差别- 为什么要监听
popstate
事件 - 传统 hash 路由为什么不够用
- HTML5 history API 的巨大优势
这就是你成为前端路由高手的第一步!
如果你觉得这篇文章对你有帮助
请给我点个赞👍,收藏一下,转发给正在迷茫的同学们!
让更多人一起打破"刷新页面才跳转"的迷思,拥抱前端技术的美好未来!