彻底搞懂前端路由:从 Hash 到 History 的演进与实践
2025-08-03
1731 字约 6 分钟
...一、什么是路由?
在 Web 开发中,路由(Route) 是 URL 与资源 之间的映射关系。
- 服务端路由:传统多页应用(MPA)里,浏览器每跳转一次 URL,都会向服务器发起一次新的请求,服务器根据 URL 返回不同的 HTML 页面。
- 前端路由:单页应用(SPA)里,浏览器只加载一次
index.html,后续 URL 变化完全在客户端完成,不触发整页刷新,通过 JS 动态渲染局部视图仅局部更新 DOM,不刷新页面。
二、SPA 为什么需要前端路由?
| 维度 | 多页应用(MPA) | 单页应用(SPA) |
|---|---|---|
| URL 与 资源 映射 | 一个 URL 对应一份 HTML | 一个 URL 对应一份组件 |
| 页面切换 | 整页刷新 | 局部 DOM 替换 |
| 体验 | 白屏、状态丢失,割裂 | 无缝、沉浸 |
| SEO | 天然友好 | 需 SSR/预渲染 |
✅ 结论:前端路由让 SPA 既拥有“不刷新”的流畅体验,又能在浏览器地址栏正确反映当前视图状态。
三、前端路由要解决的两个核心问题
1. 如何感知 URL 变化?
浏览器原生并不会在 URL 变化时主动通知我们,需要手动监听。
- Hash 模式监听
hashchange - History 模式监听
popstate
2. 如何根据 URL 找到并渲染对应组件?
需要维护一个路由表,路由表中存储了 URL 和组件的映射关系。当 URL 发生变化时,根据路由表找到对应的组件。
四、Hash 模式:最简单的前端路由
4.1 原理
URL 中 # 后面的部分称为 Hash。
#及之后的内容不会被发送到服务器,因此改变 Hash 不会触发页面刷新。- 浏览器原生事件
hashchange,用来实时监听 Hash 变化。
4.2 Hash 路由实现步骤
- 定义路由表
const routes = [
{
path: '/home',
component: () => '<h1>首页页面</h1>'
},
{
path: '/list',
component: () => '<h1>列表页页面</h1>'
}
]
- 监听
hashchange事件,当hash值变更时,触发hashchange事件,通过location获取#和后面的内容。在事件处理函数中,根据hash值,找到对应组件,将组件渲染到页面中。
window.addEventListener('hashchange', (e) => {
renderView(location.hash)
})
function renderView(url){
const index = routes.findIndex(item => {
return ('#' + item.path) === url
})
let routerView = document.getElementById('root')
routerView.innerHTML = routes[index].component()
}
✅ 优点:实现简单,兼容性好(IE8+)。
❌ 缺点:URL 带#,不美观;SEO 不友好;服务端无法直接拿到 Hash 部分。
五、History 模式:更优雅的现代方案
5.1 History API 简介
HTML5 新增的 window.history 提供了无刷新操作浏览器历史记录的能力。
| 方法 | 作用 | 是否触发 popstate |
|---|---|---|
pushState(state, title, url) | 新增历史记录 | ❌ |
replaceState(state, title, url) | 替换当前记录 | ❌ |
back / forward / go(n) | 浏览器或 JS 导航 | ✅ |
✅ 注意
- 只有浏览器前进/后退才会触发
popstate。 - 代码调用
pushState/replaceState后需手动渲染。
5.2 History 路由实现步骤
- 定义路由表;
- 监听
popstate事件,当 URL 发生变化时,触发popstate事件,通过location获取 URL 中的路径。
window.addEventListener('popstate', () => {
renderView(location.pathname)
})
- 页面加载完成时,调用
onLoad方法,拦截<a>标签默认行为。
onLoad方法:找到所有的链接,给每个链接添加点击事件;click点击事件:阻止默认事件,修改 URL,调用renderView方法。
window.addEventListener('DOMContentLoaded', () => {
onLoad()
})
function onLoad() {
let linkList = document.querySelectorAll('a[href]')
linkList.forEach(item => {
item.addEventListener('click', (e) => {
e.preventDefault()
history.pushState(null, '', item.getAttribute('href'))
renderView(item.getAttribute('href'))
})
})
}
- 定义
renderView方法渲染对应的组件。
function renderView(url) {
let index = routes.findIndex(item => {
return item.path === url
})
routerView.innerHTML = routes[index].component()
}
✅ 优点:URL 干净,支持 SEO,用户体验更好。
❌ 缺点:需要服务端配合(Nginx/Node 配置兜底),IE10+。
六、框架路由源码级对比
| 特性 | Vue Router | React Router | Angular Router |
|---|---|---|---|
| 模式 | hash / history / abstract | hash / history / memory | hash / history |
| 懒加载 | () => import() | React.lazy | loadChildren |
| 守卫/钩子 | 全局 / 路由 / 组件 | 自定义 Hooks | Guards & Resolvers |
| 嵌套路由 | <router-view> | <Outlet> | <router-outlet> |
| 数据获取 | beforeEnter, asyncData | loader | resolve |
七、总结
| 维度 | Hash | History |
|---|---|---|
| URL 美观度 | ❌ 带 # | ✅ 干净 |
| 浏览器兼容 | ✅ IE8+ | ⚠️ IE10+ |
| 服务端配置 | 不需要 | 需要兜底 |
| SEO | 差 | ✅ 好(可 SSR) |
| 实现复杂度 | 低 | 中 |
如果您觉得这篇文章有帮助,请点个赞吧~
相关文章
更多文章 →javascript2025-09-18
JavaScript 的多线程能力:Worker
如果你写过一些计算量稍大的 JavaScript 代码,比如图像处理、大量数据排序或者复杂的算法,你几乎肯定遇到过浏览器“卡死”的现象。点击页面没反应,动画也停了,就像整个世界都静止了。 这就是主线程被阻塞的典型后果。因为主线程既要负责执行 JavaScript,又要负责渲染页面、响应用户操作,一旦它被繁重的计算任务占满,就无暇顾及其他,用户体验便直线下降。 这个问题的根源,正是“主线程是单线程的”。那么,如何解决呢? 答案很简单:把这...
学习面试
javascript2025-09-10
手把手教你实现一个EventEmitter
你是不是也遇到过这种情况:代码里各种事件监听和触发,回调函数套了一层又一层,最后自己都理不清哪个事件先触发、哪个后触发了? 别担心,今天我就带你从零开始实现一个自己的事件触发器(EventEmitter),让你彻底掌握事件驱动的精髓,代码清晰度直接提升200%! 为什么要懂EventEmitter? 先说说为什么我们要关心这个。Node.js的核心就是事件驱动,比如文件读写、网络请求,都是通过事件来处理的。不会EventEmitter,...
学习面试
javascript2025-08-09
Web Worker 多线程魔法
你是否曾遇到过网页因为复杂计算而卡顿的情况?今天,让我们一起探索Web Worker的神奇世界,看看如何用多线程技术解决性能瓶颈,并实现流畅的图片压缩功能! 为什么需要Web Worker? 想象一下,你在一个繁忙的餐厅里,只有一位服务员(主线程)负责点餐、上菜、结账所有工作。当客人突然暴增时,这位服务员就会手忙脚乱,其他客人只能干等着… 这就是JavaScript的现状—— 单线程 !所有任务都在一个线程中排队执行,一旦遇到复杂计算(...
学习面试
javascript2025-08-07
🌐AST抽象语法树
AST 是前端开发中一个非常重要的概念,它就像是代码的“编译器”,能够帮助我们理解和操作代码。本文将深入探讨 AST 的原理和应用,带你从零开始了解 AST。 一、什么是 AST? AST(抽象语法树)是一种树形结构,用于表示程序代码的语法结构。它将代码分解为一系列的节点,每个节点代表代码中的一个元素,如变量、函数、表达式等。AST 是编译器和解释器的基础,也是代码分析和转换的关键工具。 1\. AST 的结构 AST 的结构通常包括以...
学习面试
javascript2025-08-05
打字机效果是如何实现的
分析效果 使用 AI 对话的时候,文字是一个字一个字蹦出来的?它不是等所有内容生成完毕后一次性展示,而是以一种流畅、实时的“打字机”效果呈现,这种体验的背后,隐藏着一种高效的Web技术。它就是 服务器推送事件(Server Sent Events,简称 SSE) 什么是 SSE 从根本上说,SSE 是一种允许服务器 单向 、 持续 地向客户端(浏览器)推送数据的Web技术。 可以把它想象成一个 电台广播 : 服务器 是广播电台,持续不断...
学习面试
javascript2025-08-04
Promise 从原理到实现
前言 是 异步编程的核心概念之一,它为我们提供了一种更优雅的方式来处理异步操作。 本文将全面剖析 的工作原理,并逐步实现一个符合 规范的 。 正文 一、Promise 基础概念 代表一个异步操作的最终完成(或失败)及其结果值。它有 种状态: 1. pending : ,既不是成功,也不是失败状态 2. fulfilled :意味着操作 完成 3. rejected :意味着操作 状态一旦改变就不能再变,只能从 变为 ,或从 变为 。 二...
学习面试
评论
请登录后发表评论
去登录