前端路由与浏览器历史:从多页应用到单页应用的演进

📖 一、引言

在现代前端开发中,路由(Route) 是一个核心概念。它决定了用户访问不同 URL 时,浏览器应该展示什么内容。理解路由的演进过程,是掌握前端框架(如 Vue、React)路由机制的基础。

本文将从最基础的浏览器 URL 讲起,逐步带你理解:

  • 🌐 浏览器 URL 的本质
  • 📄 传统多页应用的工作方式
  • ⚡ 单页应用(SPA)的诞生背景
  • 🔀 Hash 路由的实现原理

🌐 二、浏览器 URL 是什么?

当我们通过 navigator 对象在浏览器地址栏输入一个 URL 时,会发生以下过程:

复制代码
用户输入 URL → 浏览器发起 HTTP 请求 → 服务器响应 text/html → 浏览器渲染页面 → 浏览器历史插入一条记录

💡 关键点 :每一次请求,服务器返回完整的 HTML 文档,浏览器全量渲染整个页面,同时在浏览历史中记录一条新条目。

这就是传统 Web 应用的工作模式------万物互联,靠的是链接

html 复制代码
<a href="https://www.baidu.com">百度一下</a>

点击这个链接,浏览器就会发起一次全新的页面请求,整个页面被替换。


📄 三、传统多页应用(MPA)

3.1 工作方式

传统多页应用中,每个页面都是一个独立的 HTML 文件:

html 复制代码
<!-- index.html -->
<header>
  <nav>
    <ul>
      <li><a href="/index.html">首页</a></li>
      <li><a href="/about.html">关于我们</a></li>
    </ul>
  </nav>
</header>
<main>
  <h1>首页</h1>
</main>
html 复制代码
<!-- about.html -->
<header>
  <nav>
    <ul>
      <li><a href="/index.html">首页</a></li>
      <li><a href="/about.html">关于我们</a></li>
    </ul>
  </nav>
</header>
<main>
  <h1>关于我们</h1>
</main>

3.2 存在的问题 🤔

问题 说明
重复渲染 每次跳转都要重新加载整个页面,包括 header、nav 等公共部分
白屏闪烁 网速稍慢时,页面切换会出现明显的白屏
资源浪费 公共部分被反复请求和渲染,浪费带宽和性能
体验不佳 在移动端,这种"闪一下"的体验与原生 App 差距明显

📱 移动时代的要求:用户期望的是像原生 App 一样流畅的体验,而不是每次都"闪一下"。


⚛️ 四、单页应用(SPA)的诞生

4.1 什么是 SPA?

SPA(Single Page Application) 即单页应用。它的核心思想是:

🎯 只加载一个 HTML 页面,通过 JavaScript 动态替换页面内容,实现"无刷新"跳转。

复制代码
URL 变化 → 前端拦截 → JS 动态渲染对应内容 → 页面无刷新更新

4.2 实现思路

传统方式中,URL 和资源是一一对应的:

  • /index.html → 首页内容
  • /about.html → 关于页面

在 SPA 中,我们需要解决一个核心问题:

如何在改变 URL 的同时,不让页面刷新?

答案就是------Hash 路由


🔀 五、Hash 路由

5.1 URL 的结构

先来认识一下 URL 的完整结构:

复制代码
https://www.example.com/user/123?a=1&b=2#/page1
  ↑协议     ↑主机      ↑路径  ↑查询参数  ↑Hash

其中,Hash(哈希) 部分以 # 开头,它是 URL 的一部分,但有一个神奇的特性:

修改 URL 的 Hash 部分,页面不会刷新!

5.2 锚链接回顾

Hash 最初的用途是锚链接------在长页面中快速跳转到指定位置:

html 复制代码
<!-- 锚链接示例 -->
<a name="top"></a>
<a href="#bottom">跳转到底部</a>

<div style="height: 200vh; background-color: yellow;"></div>

<a href="#top">回到顶部</a>

<div style="height: 300vh; background-color: red;"></div>
<a name="bottom"></a>

点击"跳转到底部",页面会平滑滚动到底部区域,URL 会变成 #bottom,但页面不会刷新

5.3 监听 Hash 变化

浏览器提供了 hashchange 事件,可以监听 Hash 的变化:

javascript 复制代码
window.addEventListener('hashchange', function (event) {
  console.log('hash 改变了');
  console.log('新的 URL:', event.newURL);
  console.log('旧的 URL:', event.oldURL);
});

💡 这就是前端路由的核心机制:监听 URL Hash 的变化,根据不同的 Hash 值,渲染不同的页面内容。


🛠️ 六、手写一个 Hash 路由

下面我们来实现一个简单但完整的 HashRouter 类:

6.1 HTML 结构

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>SPA Hash Router</title>
</head>
<body>
  <header>
    <nav>
      <ul>
        <li><a href="#/page1">页面一</a></li>
        <li><a href="#/page2">页面二</a></li>
        <li><a href="#/page3">页面三</a></li>
      </ul>
    </nav>
  </header>
  <div id="container"></div>
  <script>
    // JavaScript 代码见下文
  </script>
</body>
</html>

6.2 HashRouter 类实现

javascript 复制代码
class HashRouter {
  constructor() {
    // 路由配置表:存储 hash -> callback 的映射关系
    this.routes = {};

    // 监听 hashchange 事件
    // 注意:使用 bind 绑定 this,确保 load 方法中的 this 指向实例
    window.addEventListener('hashchange', this.load.bind(this));
  }

  /**
   * 注册路由
   * @param {string} hash   - 路由路径,如 '/page1'
   * @param {Function} callback - 对应的渲染函数
   */
  register(hash, callback) {
    this.routes[hash] = callback;
  }

  /**
   * 加载路由:根据当前 hash 值执行对应的回调
   */
  load() {
    // 获取当前 hash,并去掉开头的 '#'
    let hash = location.hash.slice(1);

    if (hash) {
      let handle = this.routes[hash];
      if (handle) {
        handle.call(this);
      }
    }
  }
}

6.3 注册路由并使用

javascript 复制代码
// 创建路由实例
let router = new HashRouter();

// 获取页面挂载点
let container = document.getElementById('container');

// 注册路由
router.register('/page1', () => {
  container.innerHTML = '<h1>页面一</h1><p>这是页面一的内容</p>';
});

router.register('/page2', () => {
  container.innerHTML = '<h1>页面二</h1><p>这是页面二的内容</p>';
});

router.register('/page3', () => {
  container.innerHTML = '<h1>页面三</h1><p>这是页面三的内容</p>';
});

6.4 运行效果

复制代码
用户点击 "页面一"
    ↓
URL 变为 #/page1(页面不刷新)
    ↓
触发 hashchange 事件
    ↓
执行 load() 方法
    ↓
查找 routes['/page1']
    ↓
执行回调,更新 #container 的内容
    ↓
✅ 页面内容无缝切换!

🎯 七、核心要点总结

要点 说明
📍 URL 与资源一一对应 不同的 URL 应展示不同的内容
🔀 Hash 改变不会刷新页面 这是实现前端路由的关键特性
📡 hashchange 事件 浏览器原生提供的 Hash 变化监听机制
🧩 this 绑定 事件回调中需要注意 this 的指向问题
🏗️ 路由注册机制 通过 routes 对象建立 hash → callback 的映射

🚀 八、总结

从传统多页应用到单页应用,前端路由的演进解决了以下核心问题:

  1. 消除白屏闪烁 --- 页面切换无需刷新
  2. 提升用户体验 --- 接近原生 App 的流畅感
  3. URL 与内容对应 --- 保持可分享、可收藏的 URL 特性
  4. 按需渲染 --- 只更新变化的部分,节省资源

🎉 Hash 路由是前端路由的起点,理解了它,再去学习 Vue Router、React Router 等框架的路由实现,就会轻松很多!


📝 本文代码均已在浏览器中测试通过,可直接运行学习。

相关推荐
Escalating_xu23 分钟前
【C++类和对象(上)】从类的定义、封装与对象模型到内存对齐和 this 指针
开发语言·前端·c++
Json____25 分钟前
宿舍安全卫生检查系统:Node 全栈开发实战
java·前端·数据库·毕业设计·课程设计·毕设·wwwoop.com
晴天1630 分钟前
HTML canvas标签使用指南-Day32
前端·html
weixin1997010801634 分钟前
《alibaba.idle.isv.order.ship 接入实录:闲鱼订单发货回传的5个隐式约束》(附Python源码)
服务器·前端·python
初願致夕霞41 分钟前
五种IO模型(详解非阻塞IO与多路转接)
linux·前端·网络·数据库·tcp/ip
深圳多奥智能一卡(码、脸)通系统43 分钟前
智能梯控SDK可直接用于第三方开发对接的接口文档说明书
服务器·前端·算法
烂蜻蜓1 小时前
Flask入门教程(十五):错误处理与日志——打造健壮的Web应用
前端·python·flask
深念Y1 小时前
浏览器缩放检测的前端通用方案
前端
风骏时光牛马1 小时前
AIAgent异常故障根因分析与复盘总结
前端