入门:两套 API 定位与 SPA 路由基础
基于 WHATWG HTML(导航与会话历史)现行标准与各浏览器 Baseline 状态 · 核于 2026-07
速查
- 一句话定位:History 与 Navigation 是操作浏览器会话历史的两套原生 API——本叶讲"改址不刷页"的底层机制与 hash/history 路由原理,框架路由(Vue Router/React Router)见框架路由章。
- 会话历史(session history):当前标签页访问过的页面栈;地址栏、前进后退按钮、
history.length都是它的外显——SPA 路由的本质就是"改历史栈但不真正加载新文档"。 - History API 是什么:
window.history,2015 年起全绿;核心是pushState/replaceState(改址 + 存 state)+popstate事件(听前进后退)+history.state(读当前 state)。 - Navigation API 是什么:
window.navigation,2026-01 进 Baseline(Chrome/Edge/Firefox 147/Safari 26.2);核心是navigate事件(拦截所有导航)+entries()(读完整历史栈)+traverseTo(key)(直达任意 entry)。 - SPA 路由三件事:① 拦截导航(点链接/前进后退)不让浏览器真跳转;② 用 JS 换页面内容;③ 同步地址栏与历史栈让前进后退可用——两套 API 都是为这三件事服务。
- hash 路由:URL 形如
example.com/#/users/1,靠hashchange事件驱动;#后的变化不触发整页请求,故无需服务端配合,代价是 URL 不美、SEO 弱。 - history 路由:URL 形如
example.com/users/1,靠pushState造真路径;URL 干净、可 SEO,但刷新/直接访问会向服务端要该路径——服务端必须把未知路径 fallback 到index.html,否则 404。 - History API 四宗罪:读不到完整历史栈、改不了非当前 entry、
popstate不因pushState/replaceState触发、感知不到所有导航来源——路由库存在就是为补这些缺口。 - Navigation 四大改进:统一
navigate事件拦截所有导航、entries()读完整同源栈、每 entry 独立 state 可读写、navigate()返回 Promise 化的 committed/finished。 popstate关键坑:只在浏览器前进后退/history.go()时触发,pushState/replaceState时不触发——push 完要更新 UI 得自己调渲染函数,别指望 popstate。- state 走结构化克隆:两套 API 存的 state 都不能含函数、DOM 节点、类方法;History 的 state 在 Firefox 约 16 MiB 上限——大数据放外部存储,历史里只放 id/引用。
scrollRestoration:history.scrollRestoration = "manual"关掉浏览器自动滚动恢复,SPA 常用(自己控制滚动);默认"auto"。Navigation 的intercept({ scroll })更精细。- 何时用 History API:只要"改址 + 前进后退"、要最大兼容面、或维护存量路由库——够用且全绿。
- 何时用 Navigation API:新建 SPA 路由内核、要拦截所有导航来源、要读写完整历史栈、要 Promise 化异步路由——特性检测后启用。
- 特性检测 + 降级:
if ("navigation" in window) { /* Navigation */ } else { /* History 兜底 */ }——这是 2026 年的工程现实。 - 别和框架路由抢活:业务里用 Vue Router/React Router 就够,它们内部封装了这两套 API;本叶帮你读懂路由库替你做了什么,不是让你裸写路由。
- 同源约束:
pushState/replaceState的 URL 必须同源(跨源抛SecurityError);Navigation 的entries()也只暴露同源 entry——安全边界一致。 - 进阶顺序:本页 → History API → Navigation API 基础 → navigate 事件与拦截 → 迁移与模式 → 参考。
一、先厘清:会话历史与"改址不刷页"
浏览器每个标签页维护一条会话历史(session history)——你访问过的页面按顺序排成一个栈,地址栏显示栈顶那个,前进/后退按钮在栈里移动,history.length 是栈的长度。传统多页应用(MPA)里,每次导航都向服务端要一个新 HTML 文档、整页重载,历史栈由浏览器自动维护,开发者不用管。
单页应用(SPA)打破了这个模型:整个应用只有一个 HTML 文档,"翻页"是用 JavaScript 换掉页面内容,不向服务端要新文档。问题随之而来——如果只换内容不动历史栈,地址栏不变、前进后退按钮失灵、刷新回到首页。SPA 路由要解决的,就是"用 JS 换内容"和"浏览器历史栈"之间的同步:
- 拦截导航:用户点了应用内链接、按了前进后退,别让浏览器真的去加载新文档。
- 换内容:用 JS 根据目标 URL 渲染对应视图。
- 同步历史:把这次"翻页"写进历史栈,让地址栏正确、前进后退可用、刷新能还原。
History API 和 Navigation API 就是完成这三件事的浏览器原生工具。理解它们,才能看懂 Vue Router、React Router、TanStack Router 这些库到底在替你做什么——它们的路由内核正是对这两套 API 的封装。
与框架路由章的分工
本叶只讲原生 API 机制与路由原理。具体某个路由库怎么配路由表、怎么写守卫、怎么做懒加载,属于框架路由章(Vue Router / React Router / TanStack Router 各有专叶)与元框架章。本叶提到框架只做"它们内部用了哪套 API"的对比点到,不展开任何一个库。
二、两套 API 的定位
| 维度 | History API | Navigation API |
|---|---|---|
| 入口 | window.history | window.navigation |
| 出身 | 2015 年全绿的老牌方案 | 2026-01 进 Baseline 的新一代方案 |
| 改址不刷页 | pushState/replaceState | navigate() + intercept() |
| 监听导航 | popstate(只前进后退)+ hashchange | navigate 事件(所有导航来源) |
| 读历史栈 | 只能读当前 history.state 一个 | entries() 读完整同源栈 |
| 改 entry state | 只能改当前 entry | 任意 entry getState(),updateCurrentEntry |
| 异步生命周期 | 无(回调式) | navigate() 返回 { committed, finished } Promise |
| 兼容面 | 全绿 | 现代浏览器,需特性检测降级 |
History API 的心智极简:pushState 往历史栈里推一条"假页面"(改地址栏 + 存 state 但不刷新),replaceState 原地替换当前条,popstate 在用户前进后退时告诉你"栈顶变了、该换内容了"。二十年来所有 SPA 路由都建立在这三件套上。它的问题不在于不能用,而在于缺口太多(见下一节四宗罪),逼得每个路由库都要在上面糊一层。
Navigation API 是对"用 History API 做 SPA 路由"这件事的系统性重做。它不再让你"全局监听 click、preventDefault、手动 pushState、再手动渲染",而是提供一个 navigate 事件——无论用户点链接、提交表单、按前进后退、还是 JS 调 navigation.navigate(),都会触发它;你在事件里调 intercept({ handler }),浏览器就把地址栏更新、历史栈维护、滚动与焦点恢复都接管了,你只管在 handler 里渲染内容。
三、SPA 路由的两条路线:hash vs history
无论用哪套 API,SPA 路由在 URL 形态上都要在两种模式里选一种,这是必须先理解的分野:
3.1 hash 路由
URL 把路由信息放在 # 之后:
https://example.com/#/users/1
https://example.com/#/settings关键机制:URL 中 # 之后(fragment)的变化不会触发浏览器向服务端发请求——# 本是"页内锚点"设计,改它只在客户端跳锚点。SPA 借这个特性:改 location.hash 换路由,监听 hashchange 事件驱动渲染,服务端永远只看到 https://example.com/,无需任何后端配合。
- 优点:零服务端配置,刷新、直接访问、分享链接都不会 404,部署到纯静态托管(GitHub Pages 等)即可。
- 缺点:URL 里挂个
#不美观;传统上对 SEO 不友好(爬虫可能忽略 fragment);与真正的锚点定位有潜在冲突。
3.2 history 路由
URL 是干净的真实路径,靠 pushState 制造:
https://example.com/users/1
https://example.com/settings关键机制:history.pushState 能把地址栏改成任意同源路径且不刷新页面。URL 干净、可 SEO、语义清晰。但代价明确——当用户刷新页面、或直接输入 https://example.com/users/1 访问时,浏览器会实实在在地向服务端请求 /users/1 这个路径。如果服务端没有这个路由,就返回 404。
- 必须配置服务端 fallback:把所有未匹配到静态资源的路径都回退到
index.html,让 SPA 的 JS 接手根据location.pathname渲染对应视图。Nginx 用try_files $uri $uri/ /index.html;,其他服务器同理。 - 优点:URL 美观、SEO 友好、符合直觉。
- 缺点:需要后端/托管平台支持 fallback(原理与踩坑详见 History API 页)。
一句话:hash 路由用兼容与零配置换 URL 美观,history 路由用一条服务端 fallback 规则换干净 URL 与 SEO。现代项目多选 history 路由,静态托管无法配 fallback 时退回 hash 路由。Navigation API 本身工作在 history 路由模型上(操作真实路径),同样需要服务端 fallback。
四、三个最小示例:先建立手感
4.1 History API:改址不刷页
// 推一条新历史条目:地址栏变成 /users/1,但页面不刷新
history.pushState({ userId: 1 }, "", "/users/1");
// 读当前条目的 state(无需等 popstate)
console.log(history.state); // { userId: 1 }
// 监听前进后退:注意——上面的 pushState 不会触发它!
window.addEventListener("popstate", (event) => {
// 只有用户点前进/后退、或调 history.back()/go() 时才进这里
console.log("导航到:", document.location.pathname, "state:", event.state);
renderView(document.location.pathname); // 自己根据路径渲染
});4.2 Navigation API:一处拦截所有导航
// 特性检测:不支持就走 History API 分支(见迁移页)
if ("navigation" in window) {
navigation.addEventListener("navigate", (event) => {
// 只处理能拦截的、非下载、非跨文档的同源导航
if (!event.canIntercept || event.hashChange || event.downloadRequest) return;
const url = new URL(event.destination.url);
event.intercept({
async handler() {
// 地址栏、历史栈、滚动、焦点都由浏览器代管,你只渲染
const view = await loadView(url.pathname);
render(view);
},
});
});
}4.3 读完整历史栈:Navigation 独有
// History API 做不到——它只能读 history.state 一个
if ("navigation" in window) {
for (const entry of navigation.entries()) {
console.log(entry.index, entry.url, entry.getState());
}
}对比很直观:History API 需要你"全局监听 + 手动 push + 手动渲染 + 只能看当前 state",Navigation API 把这套收敛成"一个事件 + 一次 intercept + 完整历史栈随手可读"。
五、何时用哪个
- 只需要"改址不刷页 + 前进后退"、要最大兼容面:用 History API。它全绿、心智简单,是最保险的最大公约数;维护存量路由代码时也基本是它。
- 要做严肃的 SPA 路由内核(拦截所有导航来源、读写完整历史栈、Promise 化异步路由、配 View Transitions 做路由动画):用 Navigation API,
"navigation" in window特性检测后启用,以 History API 降级。 - 业务开发:多数时候你在框架路由层(Vue Router / React Router),用它们的声明式路由表就够——它们内部已封装这两套 API。本叶的价值是让你看懂路由库的底层,排查"刷新 404""前进后退不对""滚动没恢复"这类问题时能定位到原生机制。
下一页从最基础也最普及的 History API 开始,把 pushState/popstate/scrollRestoration 与 hash/history 路由原理逐一拆开。