前言:为什么我们会遇到「路由」这个问题?
不知道你有没有这样的体验:打开一个传统网站,点击导航栏的「首页」→ 白屏2秒 → 新页面加载完成;再点击「关于我们」→ 又白屏2秒 → 另一个新页面出现。就像每次切换频道都要重启电视,这种「割裂感」曾是前端开发的常态。
从「多页」到「单页」的进化
早期的网页开发中,每个链接都是一次对服务器的完整请求。用户点击链接后,浏览器会扔掉当前页面的所有内容,重新下载HTML/CSS/JS,再从头渲染整个页面。这种「整页刷新」的模式就像拆房子重建——即使只是想换个墙纸,也要把整栋楼推倒重来。
随着Web应用越来越复杂(想想现在的在线文档、邮箱、管理系统),这种方式暴露出致命问题:重要的用户体验缺失——需要去到后端拿到新的html,重新渲染」
而前端路由的出现,就像给网站装了"智能导航系统"——URL变化时,只更新页面需要变化的部分,让整个应用如丝般顺滑。这就是单页应用 SPA(Single Page Application) 的核心魅力
让我们一起揭开前端路由的神秘面纱,理解它如何让现代Web应用「行云流水」。
为什么URL变化会导致页面刷新?路由是如何阻止这种默认行为的?
URL变化触发刷新的底层原因
浏览器是根据URL请求资源并展示页面。当我们点击传统 <a> 标签或直接修改URL时,浏览器会执行以下流程:
- 发送新请求 :认为这是全新的资源请求(如从 /home 到 /about )
- 丢弃当前页面 :清空现有DOM、CSSOM和JavaScript执行环境
- 重新加载资源 :从服务器获取新HTML/CSS/JS并重新解析渲染
就像下面这段代码:
<nav>
<ul>
<li><a href="./1.html"><h1>Page 1</h1></a></li>
<li><a href="./2.html"><h1>Page 2</h1></a></li>
</ul>
</nav>
<main>
<h1>Page 1</h1>
<p>第一段内容</p>
</main>
当我们点击Page 1 或者Page 2时,浏览器就会重新执行上面流程。
这种行为源于早期Web设计——每个URL对应一个独立的HTML文件,就像图书馆的每本书都有唯一编号,找不同的书必须重新去书架取。
而且这种设计的性能影响可能包括以下几个方面:
- 页面加载速度:每次导航到新URL都需要重新请求整个HTML文件,导致页面刷新,增加加载时间。
- 资源重复加载:公共资源(如CSS、JavaScript、图片)在每个页面都需要重新下载,无法有效利用缓存。
- 服务器负载:每个请求都需要服务器生成并返回完整的HTML页面,增加服务器处理压力。
- 用户体验:频繁的页面刷新会导致视觉中断,影响用户体验流畅性。
- 数据传输量:相比SPA只更新部分内容,MPA每次传输完整HTML,数据量更大。
前端路由阻止默认行为的两种方案
前端路由的核心智慧在于: 改变URL但不触发浏览器默认的资源请求 ,而是用JavaScript动态更新页面内容。
1.哈希路由(hashChange):利用锚点特性
哈希路由利用URL中的 # 符号(如 xxx.com/#home ),这个符号后面的内容变化不会触发页面刷新,但会触发 hashchange 事件。
看看这个极简实现:
<!DOCTYPE html>
<html>
<body>
<!-- 导航链接 -->
<a href="#home">首页</a>
<a href="#about">关于我们</a>
<div id="content"></div>
<div id="content-container" class="content">
Welcome, click on the links above to navigate through the pages.
</div>
<div class="box" style="width: 100vh; height: 100vh;"></div>
<a href="#top">回到顶部</a>
<script>
// 监听哈希变化
const content = document.getElementById("content-container");
window.addEventListener("hashchange", () => {
console.log(window.location.hash);
// 根据哈希值显示不同内容
switch(window.location.hash) {
case "#home":
content.innerHTML = "home";
break;
case "#about":
content.innerHTML = "about";
break;
case "#contact":
content.innerHTML = "contact";
break;
default:
content.innerHTML = "default";
break;
}
})
</script>
</body>
</html>
阻止刷新的关键 :哈希变化只会触发 hashchange 事件,浏览器不会发送新请求,因此不会刷新页面。
2.历史路由(pushState):HTML5的API革新
HTML5新增的 history.pushState() 方法允许 直接修改URL而不发送请求 ,配合 popstate 事件监听历史记录变化。
就像我们的掘金页面,它更多使用HTML5 History API,URL更美观(无 # ):
- juejin.cn/post/752541… (
猜猜是谁的文章详情页)
代码实现:
// 拦截导航链接点击事件
document.querySelectorAll('a').forEach(link => {
link.addEventListener('click', (e) => {
e.preventDefault(); // 阻止浏览器默认跳转行为
const url = link.getAttribute('href');
// 修改URL但不刷新页面
history.pushState({}, '', url);
// 手动更新页面内容
updateContent(url); // 类似掘金加载新文章内容
});
});
// 监听浏览器前进/后退按钮
window.addEventListener('popstate', () => {
updateContent(window.location.pathname);
});
e.preventDefault() 取消了 <a> 标签的默认跳转, pushState 只修改URL和历史记录,不触发请求。而且取消了 URL上带有的 # 符号,显得更加美观,就像下图所示:
掘金路由实现的用户体验优势
- 无刷新切换 :浏览文章/沸点时页面不闪烁,侧边栏和导航栏保持不变
- 状态保留 :滚动位置、输入框内容等在页面切换时可保留
- 加载优化 :只请求必要数据(如文章JSON)而非完整HTML
- 历史记录支持 :前进/后退按钮正常工作
这种实现方式是通过 JavaScript 拦截 URL 变化并动态更新内容,从根本上避免了传统多页应用的整页刷新问题。
不过 pushState 是有
路径限制 的:
- 新URL必须与当前页面同源(协议、域名、端口一致)
- 不允许跨域跳转,如从 example.com 跳转到 other.com
- 可修改路径、查询参数和哈希,但不能修改协议和域名
服务器配置要求:
- 刷新页面时,浏览器会向服务器请求该URL资源
- 需服务器配置支持,将所有路由请求重定向到index.html
- 否则会出现404错误(这是相比哈希路由的主要缺点)
早期Web设计的这种模式在技术受限的年代是合理选择,但随着Web应用复杂化,逐渐被SPA架构取代,通过前端路由(如hashChange/pushState)实现无刷新页面切换,从根本上解决了这些性能问题。
总结
从URL输入到页面展示,前端路由就像交通指挥员,让页面切换高效有序:哈希路由(兼容性好)和 历史路由(URL美观)。
为什么说前端路由是SPA的"灵魂"?欢迎在评论区留下你的看法~
下一篇文章讲解 react-dom-router 是如何通过组件化思想简化路由配置。