前言
前端路由是现代单页应用(SPA)的核心机制之一,它允许在不刷新页面的情况下改变URL并渲染不同内容。下面我将详细讲解history和hash两种路由模式的表现、API以及相关概念。
1. History 路由模式
基本原理
History 路由模式是基于 HTML5 History API 实现的客户端路由方案,它通过操作浏览器的历史记录栈来改变 URL,而不引起页面刷新。这种模式消除了传统哈希路由中的 # 符号,使 URL 看起来更加自然。
核心API
1. history.pushState(state, unused, url)
详细工作机制:
- 向浏览器历史记录栈中添加一个新条目
- 仅修改 URL 和状态对象,不触发页面加载或刷新
- 新 URL 必须与当前页面同源(协议+域名+端口相同)
参数详解:
-
state:可以存储任意可序列化的数据(最大约 640k)- 可用于保存页面状态,如滚动位置、表单数据等
- 可通过
history.state或popstate事件的event.state访问
-
title:目前大多数浏览器忽略此参数 -
url:新历史条目的 URL- 可以是绝对路径或相对路径
- 如果只修改查询参数或哈希,可以使用
location.search或location.hash
使用场景:
// 导航到新页面
function navigateTo(page) {
const state = { page, timestamp: Date.now() };
history.pushState(state, '', `/pages/${page}`);
updateView(page); // 手动更新视图
}
// 修改查询参数
function updateQuery(params) {
const url = `${location.pathname}?${new URLSearchParams(params)}`;
history.pushState({ params }, '', url);
}
2. history.replaceState(state, title, url)
与 pushState 的区别:
- 不创建新的历史记录,而是替换当前记录
- 不会增加历史记录栈的长度
- 后退按钮不会返回到被替换前的 URL
典型应用场景:
// 修正当前URL(如去掉跟踪参数)
if (location.search.includes('utm_source')) {
const cleanUrl = location.pathname + location.hash;
history.replaceState(history.state, '', cleanUrl);
}
// 更新状态不改变URL
history.replaceState({ ...history.state, data: newData }, '');
3. window.onpopstate
触发条件:
- 用户点击浏览器前进/后退按钮
- 调用
history.back()、history.forward()或history.go() - 不会在
pushState或replaceState调用时触发
事件对象属性:
state:关联的历史记录状态对象type:事件类型("popstate")
高级处理模式:
window.addEventListener('popstate', (event) => {
const state = event.state || {};
console.log('Navigating to:', window.location.pathname);
// 恢复页面状态
if (state.scrollPosition) {
window.scrollTo(state.scrollPosition);
}
// 根据URL更新视图
updateViewFromURL();
});
// 保存滚动位置
window.addEventListener('scroll', () => {
history.replaceState(
{ ...history.state, scrollPosition: window.scrollY },
''
);
});
4. history.state
特点:
- 只读属性,返回当前历史记录的状态对象副本
- 初始值为
null - 状态对象会被浏览器序列化,因此每次访问返回的都是新对象
使用技巧:
// 合并更新状态
function updateState(newState) {
history.replaceState(
{ ...history.state, ...newState },
''
);
}
// 获取当前状态
function getCurrentState() {
return history.state || {};
}
当然还有一些常用的API,大家可以去看看History,里面基本有所有关于History的方法和属性,可以点击相应的链接进行查看和使用
2. Hash 路由模式
基本原理
Hash 路由模式是利用浏览器 URL 中哈希部分(# 及其后面的内容)的变化来实现客户端路由的一种技术方案。哈希部分的改变不会导致浏览器向服务器发起请求,但会创建历史记录条目,这使得它成为实现单页应用路由的理想选择。
哈希路由的工作原理
-
URL 结构解析:
- 完整URL格式:
https://example.com/#/path?query=value - 哈希部分:从
#开始到 URL 结束的所有内容 - 浏览器会将哈希部分视为页面内的锚点,不会发送到服务器
- 完整URL格式:
-
路由变化机制:
- 当哈希部分改变时,浏览器会触发
hashchange事件 - 页面不会重新加载,但会在浏览历史中添加记录
- 可以通过前进/后退按钮导航哈希变化历史
- 当哈希部分改变时,浏览器会触发
-
与传统锚点的区别:
- 传统锚点:
#section1用于页面内跳转 - 路由哈希:
#/path模拟完整路径结构,通常带有斜杠
- 传统锚点:
核心API
1. window.location.hash
详细工作机制:
- 可读写属性,用于获取或设置当前 URL 的哈希部分
- 设置新哈希值会创建新的浏览器历史记录
- 哈希值应包括
#符号(设置时可省略,但获取时包含)
参数详解:
- 获取时:返回包含
#的完整哈希字符串(如"#/home") - 设置时:可以带或不带
#("#/home"或"/home"效果相同)
使用场景:
// 基本导航
function navigateTo(path) {
window.location.hash = `#${path}`;
// 等效于 window.location.hash = path;
}
// 获取当前路由
function getCurrentRoute() {
return window.location.hash.substring(1) || '/'; // 去掉#并处理空哈希
}
// 修改哈希但不创建历史记录(替换当前记录)
function replaceHash(path) {
const href = window.location.href.replace(/(#.+)?$/, `#${path}`);
window.location.replace(href);
}
2. window.onhashchange
触发条件:
- URL 的哈希部分发生改变时触发
- 包括用户手动修改 URL、脚本修改 location.hash、浏览器前进/后退操作
- 哈希值相同不会触发(如从
#/home再次设置为#/home)
事件对象属性:
oldURL:变化前的完整 URL(部分浏览器可能不支持)newURL:变化后的完整 URL(部分浏览器可能不支持)
高级处理模式:
window.addEventListener('hashchange', (event) => {
// 兼容性处理
const oldHash = event.oldURL
? new URL(event.oldURL).hash
: previousHash;
const newHash = event.newURL
? new URL(event.newURL).hash
: window.location.hash;
console.log(`Hash changed from ${oldHash} to ${newHash}`);
// 路由处理
handleRouteChange(newHash);
// 保存当前哈希
previousHash = newHash;
});
// 手动跟踪前一个哈希(兼容不支持oldURL的浏览器)
let previousHash = window.location.hash;
特点
- 兼容性好(支持旧浏览器)
- 不需要服务器特殊配置
- URL中有#号,不太美观
- 没有同源限制
- 不能存储状态对象
3. 两种模式的比较
| 特性 | History模式 | Hash模式 |
|---|---|---|
| URL美观度 | 美观 | 有#号 |
| 服务器配置 | 需要特殊配置 | 不需要 |
| 兼容性 | IE10+ | 几乎所有浏览器 |
| 状态管理 | 支持state对象 | 不支持 |
| 同源限制 | 有 | 无 |
| SEO友好度 | 较好 | 较差 |
4. 实际应用示例
History模式实现
// 路由表
const routes = {
'/': 'Home Page',
'/about': 'About Page',
'/contact': 'Contact Page'
};
// 初始化路由
function navigate() {
const path = window.location.pathname;
document.getElementById('content').innerText = routes[path] || '404 Not Found';
}
// 监听popstate事件
window.addEventListener('popstate', navigate);
// 编程式导航
function push(path) {
history.pushState({}, '', path);
navigate();
}
// 初始加载
navigate();
Hash模式实现
// 路由表
const routes = {
'': 'Home Page',
'#/about': 'About Page',
'#/contact': 'Contact Page'
};
// 初始化路由
function navigate() {
const hash = window.location.hash || '#/';
document.getElementById('content').innerText = routes[hash] || '404 Not Found';
}
// 监听hashchange事件
window.addEventListener('hashchange', navigate);
// 编程式导航
function push(hash) {
window.location.hash = hash;
}
// 初始加载
navigate();
总结
前端路由是现代Web应用的核心技术之一。History模式提供了更干净的URL但需要服务器支持,Hash模式兼容性更好但URL不够美观。现代前端框架都有成熟的路由解决方案,理解底层原理有助于更好地使用这些工具和解决复杂路由场景。
在实际项目中,选择哪种路由模式取决于你的需求:
- 如果需要更好的SEO和美观的URL,使用History模式
- 如果需要更好的兼容性或无法配置服务器,使用Hash模式