先说一个结论:`onpageshow`和`onpagehide`并不能直接告诉你页面是不是被刷新了,它们只是浏览器用来通知页面生命周期变化的“通用呼叫器”。
这两个事件,不是“页面刷新专用钩子”。它们的触发场景很广——比如你把标签页切到后台、锁屏、把APP退到后台,甚至安卓上按一下Home键,这些都不是页面刷新,但`onpageshow`照样会触发,并且带着`event.persisted === true`。只有当页面真的被销毁后重建,比如手动按F5、地址栏回车、或者用JS调用`location.reload()`,`onpageshow`才会给你一个`persisted: false`。所以,光盯着`event.persisted`,你没法区分这次是“冷启动”(手动刷新)还是“热恢复”(从bfcache返回)。
那么,真正靠谱的方案是什么?
**`window.name`是唯一能在客户端稳定标记“本次是否为刷新加载”的手段。**
它的原理很简单:在一个同源标签页里,`window.name`的值跨刷新保持不丢。这玩意儿不依赖任何存储API,也不用后端配合,纯前端就能搞定。关键的操作时机是——你必须在`onpageshow`事件里去读这个值,而不是`onload`或者`DOMContentLoaded`,否则很容易漏掉从bfcache恢复的场景。
具体的逻辑链是这样:
- 当`onpageshow`触发时,第一步就是检查`window.name`是否等于`'refreshed'`
- 如果是,说明上次已经被标记过了,那这次毫无疑问就是刷新加载
- 如果不是,说明要么是页面首次打开,要么是从缓存恢复(bfcache),那你就在这里给它打上标记:`window.name = 'refreshed'`
- 至于`onpagehide`,完全不需要去清理这个值——保留它,正是为了跨刷新去识别
为什么不推荐用`sessionStorage`?这是很多人踩过的坑。
有同学会这样干:在`onpageshow`里通过`sessionStorage.setItem('refreshed', '1')`来判断。这个方案错得很典型——`sessionStorage`在bfcache恢复时,数据还在,导致首次加载被错误地判成“已刷新”。更头疼的是,iOS Safari对bfcache的行为并不统一,有时候恢复时`sessionStorage`会被清空,有时候又保留,结果没法预测。相比之下,`window.name`完全不受bfcache影响,只要标签页没关,这个值就一直在。
下面直接上一段经过验证的代码,在iOS 15+、Android Chrome 110+、微信WebView 8.0.49+都实测通过:
```ja vascript
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.name
window.addEventListener('pagehide', function() {
// 不要在这里改 window.name,否则会破坏刷新识别链路
});
```
其中最容易出错的点,就是不要在`pagehide`里重置`window.name`。这是最常踩的坑——一旦重置,下次刷新就再也分不清了。
