如果把单页应用(SPA)比作一座功能齐备的大别墅,那么前端路由就是这座别墅里的"导览员"。当你在地址栏输入 URL 时,导览员就会根据地址,精准地把对应的页面组件搬到你的眼前。在这个分发过程中,主要有两位"导览员"可供选择:老成持重的 Hash 模式和现代时髦的 History 模式。很多初学者对它们的概念一知半解,面试时被问到区别也往往卡壳。今天,我们就来一场前端路由的扫盲,看看这两大模式各自的底牌是什么,在实际开发中又该如何做出抉择。
什么是路由
路由,本质上就是描述服务器上资源路径的一串地址。当你在浏览器里敲下一个 URL,后端会根据这个路径找到对应的资源,然后返回给你。
但到了前端,事情变得有点不一样。
前端路由:让 URL 和组件建立映射
现在的主流架构是 SPA(单页应用) ------整个应用只有一个 HTML 页面,切换"页面"时不再向服务端发起整页请求,而是由 JavaScript 动态替换当前页面的内容。
这时候问题就来了:页面没刷新,但用户看到的却是不同的"页面",那 URL 怎么办?
前端路由要解决的核心矛盾就是:
页面不刷新,但 URL 要变,而且浏览器前进后退要正常工作。
所以前端路由的本质,就是构建浏览器 URL 地址和组件之间的映射关系------URL 变了,渲染对应的组件,页面本身不刷新。
怎么实现"改 URL 但不刷新页面"
浏览器给了我们两条路。
一、Hash 模式
在浏览器眼里,URL 中 # 后面的部分叫做 hash (哈希值 / 片段标识符)。关键点在于:hash 的变更不会触发页面刷新,这是浏览器天生的行为。
例子:http://127.0.0.1:5500/hash.html#/home
这个链接中的 #/home 就是hsah值。
知道了这些内容那整个流程就清晰了:
- 监听 hashchange 事件
- 当url变更,hashchange 事件就会触发,通过location.hash获取当前hash值
- 拿着这个 hash 值去事先定义好的映射表里查找对应的组件,渲染出来。
javascript
// 定义路由映射表
const routes = [
{
path: '/home',
component: () => {
return '<h2>首页页面</h2>'
}
},
{
path: '/hot',
component: () => {
return '<h3>hot page</h3>'
}
}
]
// 监听 hash 变化
window.addEventListener('hashchange', () => {
// 在routes中找到对应的path,并渲染对应的组件
rederView(location.hash)
})
function rederView (hashVal){
for(let i =0;i<routes.length;i++){
if('#'+routes[i].path == hashVal){
document.getElementById('root').innerHTML = routes[i].component()
}
}
}
优点:兼容性好,实现简单,不需要服务端配合。
缺点:URL 里永远带着一个 #,不够优雅。
二、History 模式
HTML5 给 window.history 对象新增了几个 API,让前端有能力直接操控浏览器历史记录,而不触发页面刷新:
scsspushState() 向浏览器的历史记录栈中添加一条记录 popState() 从浏览器的历史记录栈中出栈一条记录 replaceState() 替换浏览器的历史记录栈中的栈顶记录
pushState 和 replaceState 都能直接修改浏览器地址栏的 URL,并且不会导致页面刷新------这正是我们需要的。
那前进后退怎么办?浏览器在用户点击前进或后退按钮时会触发 popstate 事件,我们只需要监听它,就能感知用户的历史导航行为,然后渲染对应组件。
javascript
const routes = [
{
path: '/home',
component: () => {
return '<h2>history 首页页面</h2>'
}
},
{
path: '/hot',
component: () => {
return '<h3>history hot page</h3>'
}
}
]
window.addEventListener('DOMContentLoaded', () => {
// 默认展示
onLoad()
})
window.addEventListener('popstate',()=>{ // 监听浏览器的前进后退事件
renderView(location.pathname)
})
function onLoad() { // 路径变更渲染对应组件
let linkList = document.querySelectorAll('a[href]')
// console.log(linkList);
linkList.forEach(el => {
el.addEventListener('click', (e) => {
e.preventDefault() // 阻止默认行为
history.pushState(null, '', el.getAttribute('href')) // 进入浏览器的缓存栈
renderView(location.pathname)
})
})
}
function renderView(pathname) {
routes.forEach((item) => {
if (pathname == item.path) {
document.getElementById('root').innerHTML = item.component()
}
})
}
优点:URL 干净,没有 #,和传统多页应用的 URL 完全一致。
缺点:需要服务端配合------把所有路径都 fallback 到 index.html,否则用户直接访问或刷新深层路径会 404
总结
Hash 模式简单粗暴,靠的是浏览器的"历史遗留特性";History 模式优雅现代,靠的是 HTML5 新增的 API。理解了这两套机制,你就掌握了前端路由的底层原理------不管是 Vue Router 还是 React Router,本质上都是在这两个模式之上做了一层封装和增强。