上一世,我没搞懂History API 与数据刷新,被面试官拷打,被周天辉大人训斥。(没能让周天辉大人尽兴真是抱歉);
这一世,我携这篇博客霸气归来,势必要拿回属于我的一切!!!

原生的History API
1. history.back()
- 作用:导航到浏览器历史记录中的上一页(后退一步)。
- 行为 :等同于用户点击浏览器的后退按钮,或调用
history.go(-1)
。 - 触发事件 :会触发
popstate
事件(如果 URL 的路径或查询参数变化)。 - 对历史堆栈的影响:移动历史指针到上一个条目,不修改堆栈内容。
popstate
是浏览器历史记录变化时触发的事件,常用于单页应用(SPA)中监听路由变化。
2. history.forward()
- 作用:导航到浏览器历史记录中的下一页(前进一步)。
- 行为 :等同于用户点击浏览器的前进按钮,或调用
history.go(1)
。 - 触发事件 :与
history.back()
类似,触发popstate
事件。 - 与
back()
的区别:方向相反,操作互补。
3. history.go(n)
- 作用:跳转到相对于当前页面的历史记录位置。
- 参数 :整数
n
(正数向前,负数向后)。go(-2)
:后退两步。go(3)
:前进三步。
- 灵活性 :可一次性跳转多步,比
back()
和forward()
更灵活。 - 触发事件 :同
back()
和forward()
。
4. history.pushState(state, title, url)
-
作用:向历史堆栈添加新条目,并更新 URL(不重新加载页面)。
-
用途:适用于单页应用(SPA)动态修改 URL。
-
特点 :
- 不触发页面加载:仅修改 URL 和历史堆栈。
- 不触发
popstate
事件 :除非手动调用back()
、forward()
或用户操作。 - 同源限制:URL 必须与当前页面同源。
-
示例 :
javascripthistory.pushState({ page: 1 }, "Page 1", "/page1");
5. history.replaceState(state, title, url)
-
作用:替换当前历史条目(而非添加新条目),更新 URL。
-
用途:修改当前页面的历史记录(如重定向场景)。
-
与
pushState()
的区别 :- 不新增条目:直接替换当前条目。
- 不影响指针位置:历史堆栈长度不变。
-
示例 :
javascripthistory.replaceState({ page: 2 }, "Page 2", "/page2");
6. window.location
方法
-
location.href
/location.assign(url)
:- 作用 :跳转到新 URL,会重新加载页面。
- 区别:与 History API 不同,会触发完整页面加载。
-
location.replace(url)
:- 作用:替换当前页面到新 URL(不保留当前页面的历史条目)。
- 区别 :类似
history.replaceState()
,但会导致页面重新加载。
关键区别总结
方法/属性 | 是否重新加载页面 | 修改历史堆栈 | 触发事件 | 典型场景 |
---|---|---|---|---|
history.back() |
否 | 移动指针 | popstate |
用户手动后退 |
history.forward() |
否 | 移动指针 | popstate |
用户手动前进 |
history.go(n) |
否 | 移动指针 | popstate |
跳转多步历史记录 |
history.pushState() |
否 | 新增条目 | 无(需手动触发) | SPA 动态更新 URL |
history.replaceState() |
否 | 替换当前条目 | 无(需手动触发) | 修改当前历史记录(如重定向) |
location.href |
是 | 新增条目 | 页面加载事件 | 传统页面跳转 |
location.replace() |
是 | 替换当前条目 | 页面加载事件 | 重定向且不保留当前历史 |
事件关联
popstate
事件:- 由
back()
、forward()
、go()
或用户操作触发。 - 用于响应历史条目变化(如更新 SPA 内容)。
- 由
hashchange
事件:- 仅监听 URL 中哈希(
#
)的变化。 - 可通过
pushState()
修改哈希触发。
- 仅监听 URL 中哈希(
使用建议
- 单页应用(SPA) :优先使用
pushState()
和replaceState()
管理路由。 - 导航操作 :用
back()
、forward()
、go()
代替手动修改location.href
。 - 兼容性 :
pushState()
和replaceState()
不支持 IE9 及以下,需提供降级方案(如哈希路由)。
手撕实现
一、核心原理
- 历史堆栈:维护一个数组存储历史记录(包含状态、URL 等信息)。
- 指针控制 :通过指针(
currentIndex
)跟踪当前位置。 - 事件触发 :自定义事件(如
popstate
)通知状态变化。
二、代码实现
javascript
class CustomHistory {
constructor() {
this.stack = []; // 历史记录堆栈
this.currentIndex = -1; // 当前指针位置
this.state = null; // 当前状态
this.events = {}; // 事件监听器
}
// 添加事件监听
on(event, callback) {
this.events[event] = callback;
}
// 触发事件
trigger(event, state) {
if (this.events[event]) {
this.events[event]({ state });
}
}
// 模拟 pushState
pushState(state, title, url) {
// 生成新记录(忽略 title,仅保留 state 和 url)
const newRecord = { state: JSON.parse(JSON.stringify(state)), url };
// 清除当前指针后的记录(模拟浏览器行为)
this.stack = this.stack.slice(0, this.currentIndex + 1);
this.stack.push(newRecord);
this.currentIndex = this.stack.length - 1;
this.state = state;
// 不触发 popstate 事件
}
// 模拟 replaceState
replaceState(state, title, url) {
if (this.currentIndex === -1) return;
const newRecord = { state: JSON.parse(JSON.stringify(state)), url };
this.stack[this.currentIndex] = newRecord;
this.state = state;
}
// 模拟 back()
back() {
if (this.currentIndex > 0) {
this.currentIndex--;
this.state = this.stack[this.currentIndex].state;
this.trigger('popstate', this.state);
}
}
// 模拟 forward()
forward() {
if (this.currentIndex < this.stack.length - 1) {
this.currentIndex++;
this.state = this.stack[this.currentIndex].state;
this.trigger('popstate', this.state);
}
}
// 模拟 go(n)
go(n) {
const targetIndex = this.currentIndex + n;
if (targetIndex >= 0 && targetIndex < this.stack.length) {
this.currentIndex = targetIndex;
this.state = this.stack[this.currentIndex].state;
this.trigger('popstate', this.state);
}
}
}
三、使用示例
javascript
const myHistory = new CustomHistory();
// 监听 popstate 事件
myHistory.on('popstate', (event) => {
console.log('State changed:', event.state);
});
// 添加历史记录
myHistory.pushState({ page: 1 }, '', '/page1');
myHistory.pushState({ page: 2 }, '', '/page2');
// 后退
myHistory.back(); // 触发 popstate,state 变为 { page: 1 }
// 前进
myHistory.forward(); // 触发 popstate,state 变为 { page: 2 }
// 替换当前记录
myHistory.replaceState({ page: 3 }, '', '/page3');
// 跳转两步(此处无效,因为堆栈长度不足)
myHistory.go(-2);
四、与原生的区别
特性 | 原生 History API | 自定义实现 |
---|---|---|
依赖浏览器行为 | 是(如页面加载、安全性限制) | 完全手动控制 |
URL 自动更新 | 是 | 需手动处理(如修改地址栏) |
popstate 触发时机 |
仅 back() /forward() /go() |
可自定义(如 pushState 时) |
安全性限制 | 受同源策略限制 | 无限制 |
五、扩展方向
- URL 同步 :结合
window.location.hash
或history.pushState
实现地址栏同步。 - 持久化存储 :将堆栈保存到
localStorage
以实现页面刷新后恢复。 - 路由拦截 :添加钩子函数(如
beforeNavigate
)控制导航行为。 - SPA 集成:配合前端框架(React/Vue)实现路由跳转和组件渲染。
什么会导致API接口刷新
一、用户主动行为
-
页面刷新(F5 / 浏览器刷新按钮)
强制重新加载页面,触发所有初始化 API 请求(如页面加载时的
useEffect
、componentDidMount
)。 -
表单提交(Submit)
提交表单时通常会向 API 发送 POST/PUT 请求,更新数据并可能触发后续 GET 请求。
-
手动点击按钮/链接
例如"搜索"按钮触发
fetch()
,或导航菜单跳转路由时调用 API。
二、前端隐式行为
-
前端路由变化
单页应用(SPA)中切换路由时,可能自动调用 API(如 React Router 的
useParams
监听变化)。 -
状态变化触发副作用
如 React 中
useState
变化导致useEffect
重新执行并调用 API:
javascript
const [userId, setUserId] = useState(1);
useEffect(() => {
fetch(`/api/user/${userId}`); // userId 变化时刷新 API
}, [userId]);
3.定时轮询(Polling) 通过 setInterval
定期调用 API 更新数据:
javascript
setInterval(() => {
fetch('/api/data'); // 每 5 秒刷新一次
}, 5000);
4.WebSocket 或 Server-Sent Events (SSE)
服务端推送新数据通知后,前端主动请求最新数据。
三、浏览器/网络行为
-
离线恢复在线
浏览器从离线状态恢复时,可能自动重试失败的 API 请求。
-
缓存失效
当 API 响应缓存过期(如
Cache-Control: max-age=60
),浏览器重新发起请求。 -
导航历史操作
通过
history.back()
或前进后退按钮返回到页面时,若前端监听popstate
并主动调用 API:
javascript
window.addEventListener('popstate', () => {
fetch('/api/data'); // 后退/前进时刷新
});
四、后端触发行为
-
Webhook 回调
- 第三方服务通过 Webhook 通知数据变更,前端据此主动刷新 API(如支付状态更新)。
-
Token 过期
- 若 API 返回
401 Unauthorized
,前端可能自动刷新 Token 并重试请求。
- 若 API 返回
五、代码主动调用
-
显式调用请求函数
javascript// 手动触发 API 刷新 const reloadData = () => { fetch('/api/data'); };
-
框架内置方法
如 Vue 的 vm.$forceUpdate() 可能间接触发 API 调用(若与响应式数据绑定)。
六、避免过度刷新的优化策略
- 防抖(Debounce)与节流(Throttle)
限制高频操作(如搜索框输入)的 API 请求次数。
- 缓存策略
使用 localStorage
或内存缓存避免重复请求相同数据。
- 条件请求
通过 If-Modified-Since
或 ETag
让服务端决定是否返回新数据。
- 自动刷新的开关控制
提供"暂停更新"按钮供用户选择是否停止轮询。

" 那些我曾对你承诺的所以,隐藏在黑夜中把我吞没"
end....