开篇
在日常业务开发中,时常会遇到这样一场景:浏览器标签页之间进行通信交互。
比如有一个 list
页面,列表中的每一项数据经过点击后,以新打开浏览器 Tab
标签页方式显示这条数据的 detail
页面;
detail page
经过编辑后,可通过点击右上角 更新
按钮,关闭当前页面,进入并通知 list page
重新请求更新列表数据。
这里,就涉及到两个交互:
如何关闭当前 Tab
标签页,进入其他标签页;
如何通知其他标签页去更新数据。
下面,我们一起了解一下实现过程。
标签页之间通信
首先,我们来看下两个标签页之间如何进行通信,即:关闭 detail page
后,如何让 list page
接到通知去做更新处理。
我们定义两个页面:list.html
和 detail.html
。
list.html
页面中有一个打开 detail.html
的按钮:
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"></head><body> <button onclick="openDetail()">打开 detail 页面</button> <script> function openDetail() { window.open('file://detail.html'); } </script></body></html>
detail.html
页面中有一个返回 list.html
的按钮:
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"></head><body> <button onclick="backList()">返回 list 页面</button> <script> function backList() { // ... } </script></body></html>
当我们点击 detail.html
中的 返回 list 页面
按钮后,期望在 list.html
页面中能够接收到通知。
在两个页面同源的情况下,可以借助于 localStorage
来实现。
通常我们使用 localStorage
作为数据本地存储,除了这一特性外,它还提供了监听事件来监听 localStorage
中数据的变化。
因此我们可以:点击 返回 list 页面
时给 localStorage
中设置一条数据,在 list.html
中可以监听这条数据去做更新处理。
具体实现如下:
// detail.htmlfunction backList() { localStorage.setItem("reload-list", Math.random());}// list.htmlwindow.addEventListener("storage", (e) => { if (e.key === 'reload-list') { alert('重新请求接口,刷新 list 数据!'); }})
关闭当前 Tab 标签页
当点击 detail.html
右上角更新按钮后,我们期望关闭 detail.html
页面,回到 list.html
页面。
如何关闭当前标签页呢?
其实浏览器全局对象 window
提供了这个方法:window.close()
。
但是这个方法生效需要有一个前提:当前页面必需以弹出窗口的方式打开,如通过 window.open()
打开当前页。
如果以 非弹出窗口 方式打开(复制链接直接在地址栏打开),调用 close()
时会在控制台看到如下警告:
Scripts may close only the windows that were opened by them.
在上面 list.html
中点击按钮是通过 window.open
打开 detail.html
,因此在 detail.html
中添加关闭逻辑可以生效:
function backList() { localStorage.setItem("reload-list", Math.random()); window.close();}
那么,非弹出窗口 方式打开 detail.html
此时该如何处理呢?这一场景我们可能就会选择:采用重定向方式在 detail.html
页面中去打开 list.html
页面。
那么,如何判断当前页面是以 弹出窗口
还是 非弹出窗口
打开呢?
通过 window.opener
变量可以解决。
如果新标签页面是当前页面以 window.open
方式打开,那么 window.opener
则指向当前页面,即 list.html
。
如果新标签页面不是采用 window.open
方式打开,window.opener
则为 null。
我们改写逻辑如下:
function backList() { localStorage.setItem("reload-list", Math.random()); if (window.opener) { window.close(); } else { alert('无法关闭,执行重定向 list 逻辑'); }}
打开指定 Tab 标签页
其实,上面在关闭 detail.html
时,可能会有一个问题,list.html
和 detail.html
Tab 标签页之间可能存在其他页面,在关闭了 detail.html
后,并未将标签页打开在 list.html
中。
打开 list.html
这一逻辑如何实现呢?
其实,window.open()
除了使用第一参数传递 链接 url
能够打开页面外,第二参数 name
可以打开已存在的 Tab
标签页。
我们在 list.html
页面为其设置 name
属性:
window.name = "list";
在 detail.html
页面中可以通过打开 name
的方式来切换 Tab
标签页。
function backList() { localStorage.setItem("reload-list", Math.random()); if (window.opener) { window.open('', 'list'); window.close(); } else { alert('无法关闭,执行重定向 list 逻辑'); }}
判定是否已打开指定 Tab 标签页
可能会有这么一场景:list.html
页面打开 detail.html
页面后,首先将 list.html
给关闭了,然后在 detail.html
中点击返回关闭当前页面。
此时大家可能会想到结论:当点击更新时,detail.html
也被关闭了,这就并没有实现,回到 list.html
这一场景。
好在,如果 list.html
被关闭了,在 detail.html
中拿到的 window.opener
会被置为 null,因此会命中重定向 list.html
页面逻辑。
function backList() { localStorage.setItem("reload-list", Math.random()); if (window.opener) { // window.opener === null // ... } else { alert('无法关闭,执行重定向 list 逻辑'); }}
但是,如果当前 Tab
标签页中有 list.html
,而 detail.html
是通过复制链接在浏览器 url 地址栏打开的,这将无法做到关闭这个 detail.html
,因为它并非采用 弹窗方式 打开。
最后
感谢阅读,如有不足之处,欢迎指正 ? 。
原文:https://juejin.cn/post/7099805954488664100