五一放假前接到一个需求,新版的APP频道页面上线了,需要把旧版页面A重定向到新版页面B去。由于不是服务端渲染的,只要在前端通过JS做一下重定向就可以了。

听起来很简单,也就是1行代码的事儿嘛。

1
location.href = '页面B地址';

考虑到用户进入到页面B后会点返回键,如果用location.href会导致返回后又重新跳到页面B,这里应该用location.replace才对。

1
location.replace('页面B地址');

看起来好像没问题了,然而在大多数Android的WebView中,用location.replace的效果与location.href是一样的,依然存在循环跳转的问题。

history.replaceState

要解决这个问题,可以采用history.replaceState这个API,history.replaceState能修改当前页面保存在历史记录里的url,于是只要改成

1
2
history.replaceState({}, '', '页面B地址');
location.reload();

就能神不知鬼不觉地把页面A的历史记录替换成B,并且跳转到B,返回时也不会经过A。

然而,这一切可以实现的前提是,页面A与页面B同域。不幸的是,A与B不同域。

结合sessionStorage和时间戳

既然如此又有个办法,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const now = new Date().getTime();
// getUrlParam是从url获取参数值的函数
const tsFromParam = getUrlParam('redirect-ts');
// url上有时间参数,说明是从B页面返回的,此时不重定向,而是继续返回到A之前的页面
if (tsFromParam && sessionStorage.getItem(`redirect-${tsFromParam}`)) {
history.back();
return;
}
// 再把历史记录改了,返回的url就会带redirect-ts了
// buildUrl作用是向location.herf添加redirect-ts=${now},生成新url
history.replaceState({}, '', buildUrl(location.herf, {
'redirect-ts': now,
}));
// sessionStorage添加记录
sessionStorage.setItem(`redirect-${now}`, 'flag');
// 跳转
location.replace('页面B地址');

history.back的问题

还有个问题,如果页面A是WebView第1个打开的页面,那么history.back执行后没有任何效果,会导致返回后停留在页面A,我们加个判断来优化?

1
2
3
4
5
6
7
8
if (tsFromParam && sessionStorage.getItem(`redirect-${tsFromParam}`)) {
if (history.length <= 1) {
// 关闭WebView,不同APP中不同,这里使用伪代码
JSBridge.closeWebView();
}
history.back();
return;
}

history.length的问题

以上犯了个错误,history.length不能用来判断是否能返回。在很多浏览器中,history.length只表示你看过了多少个页面,而不是还能返回多少个页面。比如你从页面C跳转到页面D,再后退返回页面C,此时history.length是3而不是1。

那么怎么判断是否能返回呢?WebView实现各不相同,没什么好办法,要么让终端提供JSAPI查询,要么就只能

1
2
3
4
5
6
7
if (tsFromParam && sessionStorage.getItem(`redirect-${tsFromParam}`)) {
history.back();
setTimeout(() => {
JSBridge.closeWebView();
}, 1000);
return;
}

执行history.back();后如果还在当前页面,就关闭WebView。setTimeout大法好。