Javascript-BF-Cache

2,475 阅读2分钟

Issue

今天遇到一个问题是:
假设有A,B,C三个页面

A:App界面
B:登录界面
C:主页面

在A页面中使用一段代码用于检测当前用户是否已经登录,如果登录,请求API获取数据;如果未登录,则会转向登录界面。

//React componentDidMount 内部

 if (isUserLoggedIn) {
    fetchData();
 } else {
    redirectToLogin();
 }

重现:
Safari浏览器中:当进入A页面后,未登录,跳转到B,登录后,跳转回A,此时在A界面点击注销,会跳转到C界面。此时,如果在手动点击浏览器上的后退按钮,发现我们上面的这一段代码并没有执行。我们期待的是可以在再次跳转到登录界面的。

分析/原理

Google后发现:
这是因为一个safari浏览器自己的一个实现,根据Firefox的1.5的一个新功能bc-cache(back-forward cache)developer.mozilla.org/en-US/Firef…
叫做:往返缓存。可以在用户使用浏览器的后退前进按钮时加快页面的转换速度。这个缓存中不仅保存着页面数据,还保存了DOM和JavaScript的状态;实际上是将整个页面都保存在了内存里。如果页面位于bfcache中,那么再次打开该页面就不会触发load事件。

所以,我们在测试时发现,我们的代码块根本就没有执行。

Firefox提供了新的事件:
第一个事件就是pageshow,这个事件在页面显示时触发,无论页面是否来自bfcache。在重新加载页面中,pageshow会在load事件触发后触发;而对于bfcache中的页面,pageshow会在页面状态完全恢复的那一刻触发。

当页面加载bfcache时onload事件不会被触发。相反,可以检查onpageshow事件的持久性属性值persisted
对于pageshow事件,初始页面加载时persisted值为false;如果页面是从bfcache中加载的,那么persisted的值就是true;

测试得到的事实:

  • 不是每一次的回退都是从缓存读取值
  • 这个issue在firefox、safari、chrome的表现不一致

解决思路:
页面在点击回退按钮时,会触发onpageshow事件,如果这个页面是从缓存加载的,那么persisted的值为true,此时调用reload,强制刷新一遍即可。

解决

html中加入如下代码:
方法一:使用jquery

<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<script>
//jquery中没有这个对象,需要用原生事件对象
$(window).bind("pageshow", function(event) {
    if (event.originalEvent.persisted) { // 从缓存加载
      window.location.reload();  //强制刷新
    }
});
</script>

方法二:不使用jquery

window.addEventListener('pageshow', function(event) {
    if (event.persisted) {// 从缓存加载
      window.location.reload();
    }
}, false);

参考

1.stackoverflow.com/questions/5…