从白屏到丝滑:前端「路由」如何拯救Web体验?(原生JS篇)

135 阅读6分钟

前言:为什么我们会遇到「路由」这个问题?

不知道你有没有这样的体验:打开一个传统网站,点击导航栏的「首页」→ 白屏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文件,就像图书馆的每本书都有唯一编号,找不同的书必须重新去书架取。

而且这种设计的性能影响可能包括以下几个方面:

  1. 页面加载速度:每次导航到新URL都需要重新请求整个HTML文件,导致页面刷新,增加加载时间。
  2. 资源重复加载:公共资源(如CSS、JavaScript、图片)在每个页面都需要重新下载,无法有效利用缓存。
  3. 服务器负载:每个请求都需要服务器生成并返回完整的HTML页面,增加服务器处理压力。
  4. 用户体验:频繁的页面刷新会导致视觉中断,影响用户体验流畅性。
  5. 数据传输量:相比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>

image.png

阻止刷新的关键 :哈希变化只会触发 hashchange 事件,浏览器不会发送新请求,因此不会刷新页面。

2.历史路由(pushState):HTML5的API革新

HTML5新增的 history.pushState() 方法允许 直接修改URL而不发送请求 ,配合 popstate 事件监听历史记录变化。

就像我们的掘金页面,它更多使用HTML5 History API,URL更美观(无 # ):

代码实现:

// 拦截导航链接点击事件
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上带有的 # 符号,显得更加美观,就像下图所示:

image.png

屏幕截图 2025-07-14 184647.png

掘金路由实现的用户体验优势

  1. 无刷新切换 :浏览文章/沸点时页面不闪烁,侧边栏和导航栏保持不变
  2. 状态保留 :滚动位置、输入框内容等在页面切换时可保留
  3. 加载优化 :只请求必要数据(如文章JSON)而非完整HTML
  4. 历史记录支持 :前进/后退按钮正常工作

这种实现方式是通过 JavaScript 拦截 URL 变化并动态更新内容,从根本上避免了传统多页应用的整页刷新问题。

不过 pushState 是有 路径限制 的:

  • 新URL必须与当前页面同源(协议、域名、端口一致)
  • 不允许跨域跳转,如从 example.com 跳转到 other.com
  • 可修改路径、查询参数和哈希,但不能修改协议和域名

服务器配置要求

  • 刷新页面时,浏览器会向服务器请求该URL资源
  • 需服务器配置支持,将所有路由请求重定向到index.html
  • 否则会出现404错误(这是相比哈希路由的主要缺点)

早期Web设计的这种模式在技术受限的年代是合理选择,但随着Web应用复杂化,逐渐被SPA架构取代,通过前端路由(如hashChange/pushState)实现无刷新页面切换,从根本上解决了这些性能问题。

总结

从URL输入到页面展示,前端路由就像交通指挥员,让页面切换高效有序:哈希路由(兼容性好)和 历史路由(URL美观)。

为什么说前端路由是SPA的"灵魂"?欢迎在评论区留下你的看法~

下一篇文章讲解 react-dom-router 是如何通过组件化思想简化路由配置。