《浏览器的时间旅行者:History API 与数据刷新的奇幻漂流》

上一世,我没搞懂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 必须与当前页面同源。
  • 示例 :

    javascript 复制代码
    history.pushState({ page: 1 }, "Page 1", "/page1");

​5. history.replaceState(state, title, url)

  • 作用:替换当前历史条目(而非添加新条目),更新 URL。

  • 用途:修改当前页面的历史记录(如重定向场景)。

  • 与 pushState() 的区别 :

    • 不新增条目:直接替换当前条目。
    • 不影响指针位置:历史堆栈长度不变。
  • 示例 :

    javascript 复制代码
    history.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() 修改哈希触发。

​使用建议

  1. 单页应用(SPA)​ :优先使用 pushState() 和 replaceState() 管理路由。
  2. 导航操作 :用 back()、forward()、go() 代替手动修改 location.href。
  3. 兼容性 :pushState() 和 replaceState() 不支持 IE9 及以下,需提供降级方案(如哈希路由)。

手撕实现

​一、核心原理

  1. 历史堆栈:维护一个数组存储历史记录(包含状态、URL 等信息)。
  2. 指针控制 :通过指针(currentIndex)跟踪当前位置。
  3. 事件触发 :自定义事件(如 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 时)
​安全性限制 受同源策略限制 无限制

​五、扩展方向

  1. URL 同步 :结合 window.location.hash 或 history.pushState 实现地址栏同步。
  2. 持久化存储 :将堆栈保存到 localStorage 以实现页面刷新后恢复。
  3. 路由拦截 :添加钩子函数(如 beforeNavigate)控制导航行为。
  4. SPA 集成:配合前端框架(React/Vue)实现路由跳转和组件渲染。

什么会导致API接口刷新

一、用户主动行为

  1. ​页面刷新(F5 / 浏览器刷新按钮)​

    强制重新加载页面,触发所有初始化 API 请求(如页面加载时的 useEffect、componentDidMount)。

  2. ​表单提交(Submit)​

    提交表单时通常会向 API 发送 POST/PUT 请求,更新数据并可能触发后续 GET 请求。

  3. ​手动点击按钮/链接

    例如"搜索"按钮触发 fetch(),或导航菜单跳转路由时调用 API。


​二、前端隐式行为

  1. ​前端路由变化

    单页应用(SPA)中切换路由时,可能自动调用 API(如 React Router 的 useParams 监听变化)。

  2. ​状态变化触发副作用

    如 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)

服务端推送新数据通知后,前端主动请求最新数据。


​三、浏览器/网络行为

  1. ​离线恢复在线

    浏览器从离线状态恢复时,可能自动重试失败的 API 请求。

  2. ​缓存失效

    当 API 响应缓存过期(如 Cache-Control: max-age=60),浏览器重新发起请求。

  3. ​导航历史操作

    通过 history.back() 或前进后退按钮返回到页面时,若前端监听 popstate 并主动调用 API:

javascript 复制代码
window.addEventListener('popstate', () => {
  fetch('/api/data'); // 后退/前进时刷新
});

​四、后端触发行为

  1. ​Webhook 回调

    • 第三方服务通过 Webhook 通知数据变更,前端据此主动刷新 API(如支付状态更新)。
  2. ​Token 过期

    • 若 API 返回 401 Unauthorized,前端可能自动刷新 Token 并重试请求。

​五、代码主动调用

  1. ​显式调用请求函数

    javascript 复制代码
    // 手动触发 API 刷新
    const reloadData = () => {
      fetch('/api/data');
    };
  2. ​框架内置方法

如 Vue 的 vm.$forceUpdate() 可能间接触发 API 调用(若与响应式数据绑定)。


​六、避免过度刷新的优化策略

  • 防抖(Debounce)与节流(Throttle)​

限制高频操作(如搜索框输入)的 API 请求次数。

  • 缓存策略

使用 localStorage 或内存缓存避免重复请求相同数据。

  • 条件请求

通过 If-Modified-Since 或 ETag 让服务端决定是否返回新数据。

  • 自动刷新的开关控制

提供"暂停更新"按钮供用户选择是否停止轮询。


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

end....

相关推荐
陈随易1 小时前
纯 Go 写的 JS 引擎,跑分超过了 C 语言的 QuickJS
前端·后端·程序员
子兮曰3 小时前
Chrome 权限元素实战:144 与 151 的渐进增强写法
前端·javascript·chrome
IT_陈寒3 小时前
Redis连接池泄漏那次,我差点把生产环境搞崩了
前端·人工智能·后端
强尼银手3 小时前
我花一天做了个格斗游戏原型,判定全对,然后被「美术」劝退了
前端·javascript
石小石Orz3 小时前
AI 正在蚕食我们的编码能力
前端·后端·算法
迪康coolmu3 小时前
出差终端数据安全落地实战:基于迪康端点安全一体化管理系统的四道防线实践
运维·服务器·经验分享·笔记·其他·安全·php
陈随易4 小时前
专注飞机 FocusFlight,两个人把番茄钟做成了一趟航班
前端·后端·程序员
总要学点什么4 小时前
第98篇:三维特效-材质线效果——给一条线换十种材质,虚线的图案只填了一半
开发语言·前端·javascript·ecmascript·gis·webgl·材质
10share5 小时前
基于大模型的 i18n 方案:把中文原文当 Key,轻松搞定多语言维护
前端·vue.js
OpsEye5 小时前
混合云 AI 架构,如何统一做敏感内容拦截?
javascript·ai编程