H5 页面跳转与刷新
H5 业务里跳转、刷新、回退看起来都是改地址,真正要分清的是:会不会进历史栈、会不会走 bfcache、页面脚本会不会重新执行。
Location 对象是浏览器用来操作 URL 的接口,window.location 和 document.location 均指向该对象;History 对象负责管理浏览器会话历史记录。
页面跳转
a 标签跳转
最基础的 HTML 跳转方式。
<!-- 当前页面跳转 -->
<a href="https://example.com/path/index.html">页面跳转</a>
<!-- 新标签页打开 -->
<a href="https://example.com/path/index.html" target="_blank">新窗口跳转</a>
阻止跳转的几种写法对比:
| 写法 | 说明 | 副作用 |
|---|---|---|
href="#" |
空片段,多数浏览器会滚到页面顶部 | 改变 hash,可能触发滚动 |
href="###" |
指向不存在的锚点,一般不会滚到顶部 | 会修改地址栏 hash;搭配 target="_blank" 会打开空白新页面 |
href="javascript:;" |
执行空 JS 语句,不做跳转 | 不修改 URL,业务里较常见 |
href="javascript:void(0);" |
void 计算表达式,返回 undefined,阻止跳转 |
不修改 URL,兼容性好 |
| 移除 href 属性 | a 标签变为普通文本标签 | 失去 a 标签原生样式,需要手动 CSS 还原 |
最佳实践:阻止跳转优先使用 href="javascript:;",或者直接移除 href、改用 click 事件。javascript: 伪协议对无障碍和内容安全策略不友好,能改成 <button> 时更合适。
location 系列跳转
location.href 可读可写,赋值新 URL 即可完成页面跳转,会向 history 新增一条历史记录。
// 当前窗口跳转
location.href = "https://example.com";
window.location.href = "https://example.com";
self.location.href = "https://example.com";
// iframe嵌套场景:父窗口页面跳转
parent.location.href = "https://example.com";
// iframe嵌套场景:顶层最外层页面跳转(跳出iframe)
top.location.href = "https://example.com";
备注:self 代表当前窗口;parent 直接父级窗口;top 最顶层窗口;多层 iframe 嵌套时三者行为差异明显。如需要跳出 iframe,务必使用 top.location.href。
meta 标签自动跳转
HTML 元标签,实现页面加载后延迟自动跳转,不需要 JS。
<!-- 5秒后自动跳转到目标地址 -->
<meta http-equiv="refresh" content="5; url=https://www.example.com">
location.assign(url)
加载指定 URL 资源,会新增历史记录,用户可以回退回到上一页。行为等价于直接赋值 location.href。
location.assign("https://example.com");
location.replace(url)
使用新 URL 替换当前完整页面 + 当前 history 历史记录,页面会整体重载,不会新增 history 记录。跳转后浏览器回退按钮无法回到上一页。
location.replace("https://example.com");
window.open()
打开新窗口、新标签页。
和 location 系列区别:location 操作当前窗口,window.open 会开启新浏览上下文。
// 打开新标签页
window.open("https://example.com");
// 在指定name的iframe窗口内加载页面
window.open("/demo.html","iframeName");
注意:浏览器弹窗拦截策略,如果不是用户手动点击触发,window.open() 会被浏览器拦截。
页面刷新
meta 标签定时自动刷新
<!-- 每5秒自动刷新页面 -->
<meta http-equiv="refresh" content="5">
history.go(0)
读取会话历史,参数为 0 代表刷新当前页。
备注:是否向服务器重新请求,由该页的 HTTP 缓存策略决定,并不保证跳过缓存。
history.go(0);
location.reload()
模拟浏览器刷新按钮,重载当前页面。
location.reload();
注意:该方法在跨域调用(执行该方法的脚本文件的域和 Location 对象所在页面的域不同)时,将会抛出 SECURITY_ERROR DOMException 异常。
注意:标准用法不传参数。Firefox 曾支持传入 true 强制走服务器请求,这不属于现行标准,Chrome、Safari 会忽略该参数,业务不要传参。
页面回退
history.back()、history.go(-1)
回到浏览器上一条历史记录,读取页面 bfcache 缓存,页面不会重新请求资源,和浏览器左上角返回按钮行为完全一致。
history.back();
history.go(-1);
注意:Safari 部分场景下单纯调用 history.go(-1) 会失效,可以配合业务逻辑做兼容,优先使用 history.back()。
document.referrer
返回来源页面完整 URI,即用户是从哪个页面跳转过来。
// 直接跳转到来源页,会新增历史记录
location.href = document.referrer;
注意:如果是直接打开地址、本地文件、跨域特殊策略,document.referrer 可能为空字符串,使用前建议做空值判断。
页面替换(不新增历史记录)
业务场景:跳转之后,不把当前页面留在 history 栈,用户回退不会再次回到本页。
location.replace(url) 直接替换页面,整个页面重载:
location.replace(url)
history.replaceState() 仅修改浏览器地址栏 URL,不会刷新页面,不会发起网络请求,属于 SPA 路由核心 API:
history.replaceState(stateObj, title, url);
参数说明:
stateObj:状态对象,popstate事件可以拿到该对象;title:绝大多数浏览器忽略该参数;url:必须和当前页面同源,否则抛出安全异常。
回退并刷新页面
业务场景举例:A 页面进入 B 页面,B 页面做数据修改;用户回退回到 A,A 必须刷新拿到最新数据。
问题根源:移动端浏览器 bfcache 往返缓存,回退页面直接读取内存缓存,JS 不重新执行,接口不会重新请求。
pageshow 事件(推荐,H5 通用)
pageshow 在页面显示时触发;persisted 属性为 true,代表页面来自 bfcache 缓存。
window.addEventListener('pageshow', function (e) {
// 从bfcache缓存恢复,执行刷新
if (e.persisted) {
location.reload();
}
})
另一种标记变量写法,pagehide 页面隐藏时打标记:
let pageHiddenFlag = false;
window.addEventListener('pagehide', () => {
pageHiddenFlag = true;
})
window.addEventListener('pageshow', () => {
if(pageHiddenFlag) {
location.reload();
}
})
visibilitychange 事件
监听页面可见性变化。当标签页从后台切回前台、从隐藏变为可见时触发。
注意:该事件不区分是「回退回来」还是「切 Tab 回来」;适合做兜底,不能单独作为回退刷新唯一方案。
document.addEventListener('visibilitychange', function () {
// 页面变为可见状态
if (!document.hidden) {
location.reload();
}
})
注意:即使页面 DOM 刷新成功,接口还可能读取 HTTP 缓存;请求接口可以追加时间戳避免缓存:/api/list?_t=${Date.now()}。
location.replace(document.referrer)
直接替换为来源页面,会重载页面,但是如果 referrer 为空会出错,需要判空。
if(document.referrer){
location.replace(document.referrer);
}
popstate 监听 + replaceState
适合 SPA 单页应用场景,监听浏览器回退 popstate 事件,回退触发时手动刷新页面。
history.replaceState({flag:1}, "", location.href);
window.addEventListener('popstate',function(){
location.reload();
})
常见疑难问题
页面回退不刷新
现象:A→B,回退A,A页面数据还是旧的。
原因:
- 浏览器 bfcache 往返缓存,页面从内存直接恢复,脚本不重新执行;
- Ajax 接口读取 HTTP 缓存;
- App 内嵌 WebView 新开窗口,history 栈互相独立。
解决:优先使用 pageshow 事件做刷新,接口增加时间戳破除 HTTP 缓存。
history.back() 和 history.go(-1) 有什么区别?
相同点:效果都是回退到上一条历史记录,大多数浏览器表现一致。
差异:
- 语义定义:
history.back()是无参数方法,语义明确,代表后退一步;history.go(delta)是通用跳转方法,支持正负数字,可以前进、后退多步,go(-1)只是传入参数实现后退一步。 - Safari、WebView 兼容性差异:部分旧版 iOS Safari、部分 APP WebView 中,
history.go(-1)偶现不生效;而history.back()兼容性更稳定。
最佳实践:只需要回退上一页优先使用 history.back();需要跨多步跳转才使用 history.go(n)。
window.location 与 document.location
- 无 iframe 嵌套时:
window.location === document.location,完全等价。 - iframe 嵌套时:
document.location代表当前 iframe 文档的 URL;top.location代表最外层窗口。
禁止浏览器页面回退
拦截鼠标回退按钮、手势、键盘回退,持续把历史记录锁在当前页面。
// 压入一条当前页面历史
history.pushState(null, null, document.URL);
window.addEventListener('popstate', function () {
// 用户回退,再压入记录,停留在本页
history.pushState(null, null, document.URL);
})
注意:只能阻止 history 栈内回退,无法阻止用户关闭标签页。
跳转改的是地址,刷新改的是文档生命周期。选 API 之前先确认要不要留下历史记录、回退时要不要重新拉数据。