人人都会AI编程

14.4 history 对象:历史记录管理、前进后退、路由跳转

更新时间:2026-07-11

在浏览器中,每次页面跳转、每次 location 变化,都会被记录在会话历史(session history)堆栈中。window.history 对象提供了对这一堆栈的编程访问能力,它既能让开发者读取历史记录信息,也能通过 API 控制浏览器的前进后退,甚至在不刷新页面的情况下修改 URL——这正是现代前端路由实现的基础。

14.4.1 基本属性

// 当前历史堆栈中的记录总数(只读)
console.log(history.length);

// 当前历史记录的状态对象(通过 pushState/replaceState 设置的)
console.log(history.state);
  • length 表示当前标签页在同一个浏览器会话中累积的历史记录数量。用户打开新标签页后,length 为 1(当前页)。
  • state 是与当前历史记录相关联的 JavaScript 对象,由 pushState()replaceState() 传入。如果没有通过这两个方法设置过状态,则为 null

这两个属性在日常开发中更多用于调试和边缘判断,实际导航主要依赖方法。

14.4.2 导航方法:go / back / forward

“前进后退”是用户最熟悉的浏览器操作,history 对象提供了对应的编程调用:

// 后退一页,等价于 history.go(-1)
history.back();

// 前进一页,等价于 history.go(1)
history.forward();

// 跳转到历史堆栈中的任意位置
history.go(-2);  // 后退两页
history.go(0);   // 刷新当前页(相当于 location.reload())

当你调用 back()forward() 时,浏览器会从会话历史堆栈中加载对应的记录并导航到该页面——注意,这可能会导致页面完全刷新,除非目标记录是由 pushState() / replaceState() 创建的(详见下一节)。

14.4.3 状态管理 API:pushState 与 replaceState

这是 history 对象中最重要、也最常被现代 Web 应用使用的两个方法。它们允许你在不刷新页面的前提下修改地址栏的 URL,并可以在历史记录中保存任意 JavaScript 对象作为“状态”。

pushState(state, title, url)

// 向历史堆栈压入一条新记录
history.pushState(
  { page: 1, from: 'list' },  // 状态对象
  '',                         // 标题(目前浏览器大多忽略此参数)
  '/page/1'                   // 新 URL(相对或绝对,必须同源)
);

执行 pushState 后,浏览器地址栏会显示 /page/1,但页面本身不会重新加载,也不会对 URL 发起请求。历史堆栈中多了一条记录,用户点击后退按钮会触发 popstate 事件(而不是直接回退到上一个页面)。

replaceState(state, title, url)

// 替换当前历史记录,不增加新记录
history.replaceState(
  { page: 2 },  // 新状态对象
  '',
  '/page/2'
);

replaceStatepushState 的区别在于它不产生新的历史记录,而是直接修改当前记录的状态和 URL。这在实现“重定向但不想污染后退堆栈”的场景中很有用,例如登录后跳转到首页并替换掉登录页的历史记录。

几点重要限制与注意事项

  • 两个方法都只能修改同源的 URL,不能跨域修改,否则会抛出 DOMException
  • 修改 URL 时,如果仅变化 hash(# 部分),浏览器不会真正导航,这与直接修改 location.hash 效果类似,但可以附带状态对象。
  • 修改路径部分(如 /page/1/page/2)时,浏览器不会加载新页面,但如果用户此时手动刷新,浏览器会按新的 URL 发起请求——这要求你的 Web 服务器能够正确处理这些“虚拟”路径(通常是配置重写规则,将请求指向入口 HTML 文件)。
  • 状态对象可以是任何可序列化的 JavaScript 值(通过结构化克隆算法保存),但应尽量控制体积,因为状态会保留在内存中直到历史记录被清除。

14.4.4 popstate 事件

当用户点击浏览器的前进、后退按钮,或者 JavaScript 调用 history.back() / go() 时,浏览器会触发 window 对象上的 popstate 事件。注意:pushStatereplaceState 不会触发 popstate

window.addEventListener('popstate', function(event) {
  // event.state 就是当初 pushState/replaceState 保存的状态对象
  console.log('location changed to:', document.location.href);
  console.log('state:', event.state);
});

popstate 事件就绪后,我们可以根据 event.state 中的信息来更新页面内容,实现“无刷新路由切换”。这是所有前端路由库(React Router、Vue Router 等)在 history 模式下的底层原理。

14.4.5 前端路由的两种实现方式

结合 historylocation 对象的特性,前端路由在实践中分化出两种主流方案:

1. Hash 模式

利用 location.hashhashchange 事件。URL 中 # 及其后面的部分不会发送到服务器,天然支持无刷新切换,兼容性极好,服务器无需做任何配置。例如 #/page/1

window.addEventListener('hashchange', function() {
  const route = location.hash.slice(1);  // '/page/1'
  // 根据 route 渲染对应组件
});

2. History 模式

利用 pushState / replaceState + popstate 事件,URL 完全干净(/page/1),无 # 符号,更符合传统网站的审美和 SEO 要求。但需要服务端配合:在找不到对应静态文件时,将所有请求重定向到入口 HTML(即“兜底规则”),否则用户直接访问 /page/1 或刷新页面会得到 404。

// 简易路由类示意
class Router {
  constructor() {
    this.routes = {};
    window.addEventListener('popstate', () => this._render());
  }

  navigate(path) {
    history.pushState({}, '', path);
    this._render();
  }

  _render() {
    const path = location.pathname;
    // 根据 path 查找并更新页面内容
  }
}

14.4.6 实际开发实践

在真实项目中,你几乎不会直接操作 history 对象来实现路由,而是使用成熟的客户端路由库:

  • React Router 使用 <BrowserRouter>(history 模式)或 <HashRouter>(hash 模式),内部封装了对 pushState / replaceStatepopstate 的监听。
  • Vue Router 提供 mode: 'history'mode: 'hash' 选项,行为与上述描述完全一致。
  • Next.js / Nuxt.js 等框架在文件系统路由的基础上,进一步封装了 history 操作,开发者几乎无感。

然而,理解这些封装背后的原生 API 非常必要:当需要实现自定义路由器、在非框架项目中处理简单的“页面切换”,或者在面试中解释前端路由原理时,history 对象的知识才是真正的底气。

14.4.7 小结

  • history.back() / forward() / go() 控制浏览器的物理前进后退,用于传统导航。
  • pushState() / replaceState() 让开发者在不刷新页面的情况下修改 URL 并携带状态对象,是 SPA 路由的核心。
  • popstate 事件在历史记录发生跳转时触发,用于响应浏览器导航操作。
  • 实际开发中推荐使用成熟的路由库,但掌握原生能力能让你从容应对定制化需求和底层排错。

理解 history 对象的运作机制,你就真正掌握了现代 Web 应用中“无刷新页面切换”的精髓。接下来,我们将进入浏览器中另一个非常重要的定时与动画控制——14.5 节定时器和 requestAnimationFrame