SPA 路由的核心在于浏览器 URL 的管理与对应视图的匹配。通常分为hash模式和history模式。
hash模式
通过触发 hashchange 事件,进而触发页面的更新逻辑。
什么是hash属性
URL 接口的 hash 属性是一个包含以 '#' 开头的 URL 片段标识符的字符串。 片段不会经过 URL 解码。如果某个 URL 没有片段,该属性会包含一个空字符串------""。
URL编码:百分号编码(英语:Percent-encoding),用于为application/x-www-form-urlencoded MIME准备数据,因为它用于通过HTTP的请求操作(request)提交HTML表单数据。
特点:
-
不发送至服务器:hash部分不会被浏览器发送到服务器。它仅在客户端使用。
-
hash值改变不会被触发页面刷新
-
可以通过hashchange事件监听hash部分变化
实现
依赖window.hashchange
方法
触发的情况:
- 修改 window.location.hash: 通过 JavaScript 直接更改 window.location.hash 的值。例如: javascript window.location.hash = '#newSection';
- 使用锚链接: 在 HTML 中,使用锚链接直接导航到新的 hash。例如:
html
<a href="#section2">Go to Section 2</a>
点击该链接后,浏览器会自动更新 URL 的 hash 部分。
-
浏览器导航按钮: 用户点击浏览器的前进或后退按钮,若 URL 的 Hash 发生改变,也会触发该事件。
-
通过 a 标签的 href 属性: 用户点击后,URL 发生变化,从而触发 hashchange 事件。
-
通过 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 数组
- history.back = history.go(-1) = 浏览器后退键
- history.forward = history.go(1) = 浏览器前进键
history.go
-
通过当前页面的相对位置从浏览器历史记录(会话记录)异步加载页面。
-
参数为 -1 的时候为上一页,参数为 1 的时候为下一页。
-
history.go(2)向前移动两页,history.go(-2)则向后移动两页。也可以理解为指针分别向栈底和栈顶移动2个位置。浏览器在往历史记录栈里面压入新的记录时,是直接在当前指针后面压入的,如果当前指针的后面,还有其它的记录项,都会被丢弃。
-
当你指定了一个越界值(例如:当会话历史记录中没有之前访问的页面时,则传参的值为 -1,那么这个方法没有任何效果也不会报错。
-
调用没有参数的 history.go() 方法或者参数值为 0 时,重新载入当前页面。 = location.reload()
history.length
属性的值来确定历史记录栈中的页面数量history.pushState
类似于window.location = "#foo",它们都会在当前的文档中保留旧条目并创建和激活一个新的历史条目。 但是不会伴随着刷新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 友好性 | 较差 | 较好 |
实现难度 | 简单,无需服务器配置 | 较复杂,不当的链接跳转可能会导致全页面重载。 |
刷新处理 | 无需特别处理 | 需要服务器端配置重定向 |