《浏览器的时间旅行者: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.hashhistory.pushState 实现地址栏同步。
  2. 持久化存储 :将堆栈保存到 localStorage 以实现页面刷新后恢复。
  3. 路由拦截 :添加钩子函数(如 beforeNavigate)控制导航行为。
  4. SPA 集成:配合前端框架(React/Vue)实现路由跳转和组件渲染。

什么会导致API接口刷新

一、用户主动行为

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

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

  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-SinceETag 让服务端决定是否返回新数据。

  • 自动刷新的开关控制

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


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

end....

相关推荐
C_V_Better18 分钟前
浏览器缓存机制:JavaScript 文件缓存导致 404 错误的解决方案
开发语言·前端·javascript·缓存
小救星小杜、21 分钟前
a = b &&c 的含义
开发语言·前端·javascript
uhakadotcom22 分钟前
Babylon.js:轻松打造Web 3D体验
前端·javascript·面试
parade岁月24 分钟前
告别代码质量隐患:Husky 生态工具链在前端工程化中的实战应用
前端·javascript
小成C24 分钟前
为什么会演化出RSC,SSR和RSC关系大解密
前端·react.js
过期的H2O225 分钟前
【H2O2 | 软件开发】Axios发送Http请求
前端·http·axios·交互
bug总结31 分钟前
vue3 public下引入图片路径打包后线上不显示问题解决
前端·javascript·vue.js
悠然青年帅34 分钟前
基于Vue+Canvas实现的画板绘画以及保存功能
前端
screct_demo37 分钟前
详细讲一下 Webpack 主要生命周期钩子流程(重难点)
前端·webpack·node.js
小妖66638 分钟前
vue2的webpack(vue.config.js) 怎么使用请求转发 devServer.proxy
javascript·vue.js·webpack