onpageshow 和 onpagehide 触发于页面生命周期变化(如切后台、锁屏、退至后台),非仅刷新;其中 onpageshow 的 event.persisted 可区分 bfcache 恢复(true)与页面重建(false),但无法单独判断是否为刷新,需结合 window.name 跨刷新持久特性识别真实刷新。
这两个事件不是“页面刷新专用钩子”,而是浏览器对页面生命周期的通用通知机制。它们在多种场景下都会触发,比如:切到后台标签页、锁屏、APP退到后台、甚至安卓上按 Home 键——这些都不是刷新,但 onpageshow 仍会带 event.persisted === true。只有当页面被真正销毁后重建(如手动刷新、地址栏回车、F5),onpageshow 才会以 persisted: false 触发。所以单看 onpageshow 的 persisted 值,无法区分“冷启动”和“热恢复”。
因为 window.name 在同源 tab 内跨刷新保持不变,且不依赖存储 API 或服务端配合,适合纯前端判断。关键点在于:必须在 onpageshow 中读取,而不是 onload 或 DOMContentLoaded,否则会漏掉从缓存恢复(bfcache)的场景。
onpageshow 触发时,先检查 window.name === 'refreshed'
window.name = 'refreshed' 并记录初始状态onpagehide 中清理?不用。保留它正是为了跨刷新识别很多人用 sessionStorage.setItem('refreshed', '1') 配合 onpageshow 判断,但这是错的:sessionStorage 在 bfcache 恢复时依然存在,导致“首次加载”误判为“已刷新”。更糟的是,iOS Safari 对 bfcache 的行为不稳定,有时恢复时 sessionStorage 被清空,有时又保留——结果不可预测。而 window.name 不受 bfcache 影响,只要 tab 没关,值就一直在线。
以下代码在 iOS 15+、Android Chrome 110+、微信 WebView 8.0.49+ 实测通过:
let isRefreshed = false;window.addEventListener('pageshow', function(e) { if (e.persisted) { // 来自 bfcache 恢复,不是刷新 isRefreshed = window.name === 'refreshed'; } else { // 页面重建:可能是刷新、F5、地址栏回车 isRefreshed = window.name === 'refreshed'; if (!isRefreshed) { window.name = 'refreshed'; } } if (isRefreshed) { console.log('检测到真实刷新'); // 执行刷新后逻辑,比如重置表单、清空临时状态 }});// 可选:onpagehide 中不做任何事,避免干扰 window.namewindow.addEventListener('pagehide', function() { // 不要在这里改 window.name,否则会破坏刷新识别链路});
注意:不要在 pagehide 中重置 window.name,那是最常踩的坑——一旦重置,下次刷新就再也分不清了。