JS-手写前端路由系列(一):深度解析 Hash 模式原理与实战

0 阅读3分钟

前言

在单页面应用(SPA)盛行的今天,前端路由是实现“无刷新页面切换”的核心。前端路由主要有两种实现方案:Hash 模式History 模式。本文将带你深度剖析 Hash 路由的底层原理,并从零开始手写一个可运行的 Hash 路由器。

一、 什么是 SPA 与前端路由?

1. SPA 的定义

SPA (Single Page Application) 指的是只有一个 HTML 页面的 Web 项目。它通过 JavaScript 动态变换 HTML 内容,而无需重新加载整个页面。

2. 传统 SPA 的痛点

由于页面不跳转,URL 始终不变,这带来了两个致命问题:

  • 无法记录状态:刷新页面会回到初始状态,浏览器的“前进/后退”按钮失效。
  • SEO 不友好:只有一个 URL 对应一个 HTML,搜索引擎难以抓取不同业务模块的内容。

3. 前端路由的救赎

前端路由通过监听 URL 的变化,在不刷新页面的前提下,实时切换视图组件。


二、 Hash 模式深度解析

1. 原理

Hash 路由通过 URL 中 # 及其后面的字符来实现。

  • 特点:Hash 值的变化不会触发 HTTP 请求(即不会向服务器发送数据)。
  • 兼容性:支持所有主流浏览器(包括低版本 IE)。

2. 核心 API

  • window.location.hash:获取或设置当前 URL 的 Hash 值(如 #/home)。
  • window.onhashchange最关键的 API。当 Hash 值改变时自动触发,是路由跳转的监听器。

三、 常用位置 API 对比

在编写路由时,我们经常混淆以下几个方法:

方法/属性描述对历史记录的影响
location.href获取或设置完整 URL新增一条记录
location.assign()导航到新 URL新增一条记录
location.replace()用新 URL 替换当前页不留下历史记录(无法回退)

四、 实战:从零手写一个 MyRouter 类

我们将通过类(Class)的封装,实现一个具备首页注册、404 兜底、异常处理功能的 Hash 路由器。实现思路如下:

  1. 创建一个路由对象,并且需要实现一个register方法为每个hash注册相应的回调函数
  2. 当不存在这个hash值时,判断为首页,需要实现一个registerIndex方法注册首页回调函数
  3. 通过监听hash值改变,从而触发相关回调函数
  4. 对路由中未注册的hash值进行兜底处理
  5. hash值改变,而触发回调函数进行异常兜底处理

1. 核心代码实现

<!doctype html>

<head> </head>

<body>
  <div id="nav">
    <a href="#/page1">page1</a>
    <a href="#/page2">page2</a>
    <a href="#/page3">page3</a>
    <a href="#/page4">不存在的页面</a>
  </div>
  <div id="content"></div>
</body>

<style>
  body {
    margin: 0;
    padding: 20px;
  }
</style>

<script lang="javascript">
  class MyRouter {
    constructor() {
      // 存储路由与回调函数的映射关系
      this.routes = {};
      // 初始化监听 hashchange 事件
      // 同时也监听 load 事件,处理初始进入页面的情况
      window.addEventListener('hashchange', this.load.bind(this), false);
      window.addEventListener('load', this.load.bind(this), false);
    }

    // 注册路由
    register(hash, callback) {
      this.routes[hash] = callback || function () { };
    }

    // 首页路由
    registerIndex(callback) {
      this.routes['index'] = callback;
    }

    // 404 兜底
    registerNotFound(callback) {
      this.routes['404'] = callback;
    }

    // 错误处理
    registerError(callback) {
      this.routes['error'] = callback;
    }

    load() {
      // 去掉 # 号
      let hash = window.location.hash.slice(1);
      let handler;

      if (!hash) {
        // 情况1:没有 hash,展示首页
        handler = this.routes.index;
      } else if (!this.routes.hasOwnProperty(hash)) {
        // 情况2:访问了未注册的路由
        handler = this.routes['404'] || (() => console.log('404 Not Found'));
      } else {
        // 情况3:正常匹配
        handler = this.routes[hash];
      }

      try {
        handler.call(this);
      } catch (e) {
        console.error('路由执行异常:', e);
        (this.routes['error'] || function () { }).call(this, e);
      }
    }
  }

  // 使用示例
  const router = new MyRouter();
  const content = document.getElementById('content');

  router.registerIndex(() => content.innerHTML = '<h1>欢迎来到首页</h1>');
  router.register('/page1', () => content.innerHTML = '<h1>这是第 1 页</h1>');
  router.register('/page2', () => content.innerHTML = '<h1>这是第 2 页</h1>');
  router.registerNotFound(() => content.innerHTML = '<h1>404 - 页面去火星了</h1>');

  // 注意:这里由于是演示,直接手动调用一次 load 处理初始 URL
</script>

五、 Hash 路由的优缺点总结

✅ 优点

  1. 兼容性极佳:老版本浏览器也支持。
  2. 部署简单:不需要服务器端做任何配置,前端直接处理。
  3. 安全:Hash 值不会传给服务器,减少了某些攻击面。

❌ 缺点

  1. 不够美观:URL 里带个 # 显得不够正规。
  2. SEO 困难:搜索引擎通常不处理锚点之后的内容。