pushState、replaceState 与 popstate
本节用一个明确的 state 对象观察历史条目、地址和 popstate 的关系。
html
<button id="next">下一页</button><output id="page"></output>
<script>
const page = document.querySelector('#page')
const render = () => { page.value = new URL(location.href).searchParams.get('page') || '1' }
document.querySelector('#next').addEventListener('click', () => {
history.pushState({ page: 2 }, '', '?page=2')
render()
})
addEventListener('popstate', render)
render()
</script>点击后再后退,记录地址变化和 popstate 日志;改成 replaceState 比较历史条目数量。
规则与边界
- 平台保证、浏览器实现和业务约定必须分开;一个浏览器中的结果不能自动推广到所有宿主。
- 每个监听器、请求、观察器、缓存和焦点操作都要有清理/取消责任;页面或组件离开后不能继续写状态。
- 用户输入、URL、HTML、消息和持久化数据按不同信任边界处理;类型正确不代表内容可信。
- 失败、取消、超时、权限拒绝、配额不足和不支持要有不同的可观察结果。
机制拆解
pushState 和 replaceState 只改变当前文档的 URL/历史状态,不会自动触发页面加载;用户点击后退或前进时才触发 popstate。状态对象应足够小且可序列化,页面初始化仍需从当前 URL 读取一次。
可运行实验
js
const render = () => console.log(location.pathname, location.search, history.state)
history.pushState({ page: 2 }, '', '?page=2')
render()
addEventListener('popstate', render)先执行 push,再点击后退,记录 popstate;改用 replaceState 比较历史条目数量,并刷新验证初始化代码是否能恢复筛选状态。
练习
- 点击按钮后记录 pushState、地址和 history.state,再点击后退记录 popstate。
- 将 pushState 改成 replaceState,比较历史条目数量。
- 说明刷新时为什么必须从当前 URL 初始化,而不能只依赖 state。 查看本节答案
下一步
完成答案核对后继续本章下一篇,并把实验日志保存在自己的项目中。