SPA路由的实现原理

SPA 路由的核心在于浏览器 URL 的管理与对应视图的匹配。通常分为hash模式和history模式。

hash模式

通过触发 hashchange 事件,进而触发页面的更新逻辑。

什么是hash属性

URL 接口的 hash 属性是一个包含以 '#' 开头的 URL 片段标识符的字符串。 片段不会经过 URL 解码。如果某个 URL 没有片段,该属性会包含一个空字符串------""。

URL编码:百分号编码(英语:Percent-encoding),用于为application/x-www-form-urlencoded MIME准备数据,因为它用于通过HTTP的请求操作(request)提交HTML表单数据。

特点:

  1. 不发送至服务器:hash部分不会被浏览器发送到服务器。它仅在客户端使用。

  2. hash值改变不会被触发页面刷新

  3. 可以通过hashchange事件监听hash部分变化

实现

依赖window.hashchange方法

触发的情况:

  1. 修改 window.location.hash: 通过 JavaScript 直接更改 window.location.hash 的值。例如: javascript window.location.hash = '#newSection';
  2. 使用锚链接: 在 HTML 中,使用锚链接直接导航到新的 hash。例如:
html 复制代码
 <a href="#section2">Go to Section 2</a>

点击该链接后,浏览器会自动更新 URL 的 hash 部分。

  1. 浏览器导航按钮: 用户点击浏览器的前进或后退按钮,若 URL 的 Hash 发生改变,也会触发该事件。

  2. 通过 a 标签的 href 属性: 用户点击后,URL 发生变化,从而触发 hashchange 事件。

  3. 通过 JavaScript 的 location.assign() 方法: 利用 location.assign() 或 location.replace() 来导航到新的 Hash。例如:

javascript 复制代码
 location.assign('#yetAnotherSection');
 location.replace('#yetAnotherSection');

注意: history.pushState() 从未引起 hashchange 事件的触发,即使新 URL 与旧 URL 仅在 hash 上不同。

history模式

利用浏览器的历史记录栈改变当前页面的 URL, 同时,利用点击事件 结合 window.popState 监听事件触发页面的更新渲染逻辑。

History API

History 对象提供了操作浏览器会话历史(浏览器地址栏中访问的页面,以及当前页面中通过框架加载的页面)的接口。

History 接口允许操作浏览器的曾经在标签页或者框架里访问的会话历史记录。

chrome 浏览器中,可以将鼠标按住后退按钮,查看到 History 数组

  1. history.back = history.go(-1) = 浏览器后退键
  2. history.forward = history.go(1) = 浏览器前进键
  3. history.go
  • 通过当前页面的相对位置从浏览器历史记录(会话记录)异步加载页面。

  • 参数为 -1 的时候为上一页,参数为 1 的时候为下一页。

  • history.go(2)向前移动两页,history.go(-2)则向后移动两页。也可以理解为指针分别向栈底和栈顶移动2个位置。浏览器在往历史记录栈里面压入新的记录时,是直接在当前指针后面压入的,如果当前指针的后面,还有其它的记录项,都会被丢弃。

  • 当你指定了一个越界值(例如:当会话历史记录中没有之前访问的页面时,则传参的值为 -1,那么这个方法没有任何效果也不会报错。

  • 调用没有参数的 history.go() 方法或者参数值为 0 时,重新载入当前页面。 = location.reload()

  1. history.length 属性的值来确定历史记录栈中的页面数量
  2. history.pushState 类似于window.location = "#foo",它们都会在当前的文档中保留旧条目并创建和激活一个新的历史条目。 但是不会伴随着刷新
  3. history.replaceState replaceState将当前的会话页面的url替换成指定的数据,replaceState后也会改变当前页面的url,但是也不会刷新页面。

history.replaceState() 的操作与 history.pushState() 完全相同,只是 replaceState() 会修改当前历史记录条目而不是创建新的条目。就是都会改变当前页面显示的url,但都不会刷新页面。

popstate事件

history.back()、history.forward()、history.go()事件是会触发popstate事件的,但是history.pushState()、history.replaceState()不会触发popstate事件。

在单页应用中使用的是replaceState和pushState,如何监听replaceState和pushState行为

ts 复制代码
      (function (history) {
        // 自执行函数 (Immediately Invoked Function Expression, IIFE)
        // 用于避免污染全局作用域并立即执行包裹的代码
        // 传入 window.history 对象以便在函数内部进行操作
        var originalPushState = history.pushState;
        var originalReplaceState = history.replaceState;

        // 重写 pushState 方法
        history.pushState = function (state) {
          // 如果 history.onpushstate 是一个函数,则调用它
          if (typeof history.onpushstate == "function") {
            history.onpushstate({ state: state });
          }
          // 调用原始的 pushState 方法以保留其原有功能
          return originalPushState.apply(history, arguments);
        };

        // 重写 replaceState 方法
        history.replaceState = function (state) {
          // 如果 history.onreplacestate 是一个函数,则调用它
          if (typeof history.onreplacestate == "function") {
            history.onreplacestate({ state: state });
          }
          // 调用原始的 replaceState 方法以保留其原有功能
          return originalReplaceState.apply(history, arguments);
        };
      })(window.history);

      // 设置一个通用的事件处理函数用于拦截所有状态变化事件
      window.onpopstate =
        history.onpushstate =
        history.onreplacestate =
          function (event) {
            console.log("State changed:", event.state);
          };

两种不同模式的对比

特性 Hash 模式 History 模式
URL 美观性 较差,包含 # 美观,与传统 URL 一致
SEO 友好性 较差 较好
实现难度 简单,无需服务器配置 较复杂,不当的链接跳转可能会导致全页面重载。
刷新处理 无需特别处理 需要服务器端配置重定向
相关推荐
橙子家28 分钟前
浏览器缓存之【结构化数据库与缓存】: IndexedDB、Cache storage 和 Storage buckets
前端
user205855615181334 分钟前
X6 中边悬浮置顶,规避 `mouseleave` 事件丢失问题
前端
李明卫杭州35 分钟前
CSS aspect-ratio 属性完全指南
前端
Pedantic3 小时前
SwiftUI 手势层级(Gesture Hierarchy)详解
前端
飘尘3 小时前
前端转型全栈(Java后端)的快速上手指引
前端·后端·全栈
一颗烂土豆3 小时前
Meshopt 压缩深度解析,为什么它比 Draco 更快
前端·javascript·webgl
浏览器工程师4 小时前
AI Agent 接浏览器任务,先别让它一路点到底
前端·后端
雨季mo浅忆4 小时前
VSCode自动格式化三要素
前端
爱勇宝5 小时前
深扒 Anthropic 1680 位工程师简历:应届生几乎没机会,AI 公司最缺的不是博士
前端·后端·程序员