1. 什么是前端路由
前端路由是通过url路径来匹配对应的代码块的这么一套机制
- 修改 url 后页面要更新
- 浏览器不能刷新
- 如何监听url变更
2. 为什么要使用前端路由?
传统网页(非单页应用)每次跳转都要重新加载整个页面,就像每次翻书都要重新印刷整本书一样浪费资源。而前端路由的优点是:
- 页面切换更快(只更新变化的部分)
- 用户体验更好(类似原生 App 的流畅感)
- 更适合构建单页应用(SPA)
3. hash路由
3.1 识别特征
URL 中带 # 号,比如:
https://example.com/#/home
https://example.com/#/about
3.2 工作原理
- 改变
#后的内容不会触发页面刷新(这是浏览器的天然特性) - 通过监听
hashchange事件感知 URL 变化 - 根据不同的 hash 值显示对应内容
3.3 demo演示
<body>
<!-- 导航链接:通过 # 的方式切换路由 -->
<ul>
<li>
<!-- 点击后地址栏会变成 #/home -->
<a href="#/home">首页</a>
</li>
<li>
<!-- 点击后地址栏会变成 #/about -->
<a href="#/about">关于</a>
</li>
</ul>
<!-- 内容显示区域 -->
<div id="app"></div>
<script>
// 路由配置表:定义每个路径对应的组件
const routes = [
{
path: '/home', // 路径
component: () => { // 对应的组件(返回HTML字符串的函数)
return '<h2>首页页面</h2>';
}
},
{
path: '/about',
component: () => {
return '<h3>about页面</h3>';
}
}
]
// 页面首次加载时执行:读取当前 hash 并显示对应内容
window.addEventListener('DOMContentLoaded', () => {
// location.hash 格式是 "#/home" 这种带 # 的
routerView(location.hash)
})
// 监听 hash 变化:当点击链接或手动修改 hash 时触发
window.addEventListener('hashchange', () => {
routerView(location.hash)
})
// 获取内容容器元素
const app = document.querySelector('#app');
// 核心路由函数:根据 hash 显示对应内容
function routerView(localHash) {
// 在路由表中查找匹配当前 hash 的项
const index = routes.findIndex((item) => {
// 拼接成 "#/path" 的形式进行比较
return '#' + item.path === localHash
})
// 如果找到匹配项(index不是-1),渲染对应组件
if (index !== -1) {
app.innerHTML = routes[index].component()
} else {
// 这里可以添加404处理(当前demo暂未实现)
app.innerHTML = '<p>页面不存在</p>'
}
}
</script>
</body>
关键流程说明:
- 用户点击链接 -> 修改地址栏 hash(例如变成
#/home) - 触发 hashchange 事件 -> 调用 routerView 函数
- 查找匹配路由 -> 在 routes 数组中找 path 对应的配置项
- 渲染组件 -> 执行对应组件的函数,将返回的HTML插入到页面
需要注意的小细节:
location.hash的值始终以#开头(比如#/home)findIndex方法如果找不到会返回 -1(需要处理未匹配的情况)- 直接修改地址栏的 hash 也会触发页面内容变化(比如手动输入#/about)
3.4 优缺点
- ✅优点:兼容性好(连 IE8 都支持)
- ❌ 缺点:URL 中有
#号不够美观
4. history 路由
4.1 工作原理
首先要阻止 a 标签的默认行为,然后使用 history.pushState() 来改变 url 路径,它是不会引起页面的重新加载的,在 url 变更后,读取本地的 pathname 来判断要展示的组件
考虑到浏览器的前进后退按钮的存在,需要监听 popstate 事件,来判断要展示的对应的组件
4.2 demo演示
<body>
<!-- 导航链接:通过普通路径的方式切换路由 -->
<ul>
<li>
<!-- 点击后地址栏会变成 /home -->
<a href="/home">首页</a>
</li>
<li>
<!-- 点击后地址栏会变成 /about -->
<a href="/about">关于</a>
</li>
</ul>
<!-- 内容显示区域 -->
<div id="app"></div>
<script>
// 路由配置表:定义每个路径对应的组件
const routes = [
{
path: '/home', // 路径
component: () => { // 对应的组件(返回HTML字符串的函数)
return '<h2>首页页面</h2>';
}
},
{
path: '/about',
component: () => {
return '<h3>about页面</h3>';
}
}
]
// 监听浏览器前进/后退按钮:当用户点击前进或后退时触发
window.addEventListener('popstate', function () {
// 调用 routerView 函数,根据当前路径更新页面内容
routerView()
})
// 获取所有 a 标签
let linkList = document.querySelectorAll('a')
// 为每个 a 标签绑定点击事件
linkList.forEach(link => {
link.addEventListener('click', function (e) {
// 阻止 a 标签的默认跳转行为(避免页面刷新)
e.preventDefault()
// 使用 history.pushState 修改浏览器地址栏的 URL
// 参数说明:
// 1. null:不需要传递额外数据
// 2. '':不需要修改标题
// 3. this.getAttribute('href'):获取 a 标签的 href 属性值(如 /home)
history.pushState(null, '', this.getAttribute('href'))
// 调用 routerView 函数,更新页面内容
routerView()
})
})
// 获取内容容器元素
const app = document.getElementById('app')
// 核心路由函数:根据当前路径显示对应内容
function routerView() {
// 在路由表中查找匹配当前路径的项
const index = routes.findIndex(item => {
// location.pathname 是当前 URL 的路径部分(如 /home)
return item.path === location.pathname
})
// 如果找到匹配项(index不是-1),渲染对应组件
if (index !== -1) {
app.innerHTML = routes[index].component()
} else {
// 这里可以添加404处理(当前demo暂未实现)
app.innerHTML = '<p>页面不存在</p>'
}
}
</script>
</body>
关键流程说明:
- 用户点击链接 -> 触发点击事件
- 阻止默认跳转 -> 使用
e.preventDefault()防止页面刷新 - 修改 URL -> 使用
history.pushState修改地址栏路径 - 更新页面内容 -> 调用
routerView函数,根据当前路径渲染对应组件
需要注意的小细节:
-
history.pushState的作用:- 修改浏览器地址栏的 URL
- 不会触发页面刷新
- 会添加一条历史记录
-
popstate事件的作用:- 监听浏览器的前进/后退操作
- 当用户点击前进或后退时,更新页面内容
-
location.pathname的值:- 是当前 URL 的路径部分(如
/home或/about) - 不包含域名和查询参数
- 是当前 URL 的路径部分(如
-
服务器配置:
- 如果用户手动刷新页面,浏览器会向服务器请求当前路径(如
/home) - 需要服务器配置,将所有路径返回
index.html,否则会 404
- 如果用户手动刷新页面,浏览器会向服务器请求当前路径(如
4.3 优缺点
-
✅ 优点:URL 更简洁美观
-
❌ 缺点:
- 兼容性稍差(不支持 IE10 以下)
- 需要服务器配置(所有路径返回 index.html)