多标签间通信及共享 Session 方案

2,457 阅读4分钟

一. 实现多标签通信的几种方案

1. BroadcastChannel

引 MDN:

Broadcast Channel API 可以实现同  下浏览器不同窗口,Tab页,frame或者 iframe 下的 浏览器上下文 (通常是同一个网站下不同的页面)之间的简单通讯。

image.png

1) 用法:

// 连接到广播频道
var bc = new BroadcastChannel('test_channel');

// 发送消息
bc.postMessage('This is a test message.');

// 接收消息
bc.onmessage = function (ev) { console.log(ev); }

// 断开频道连接
bc.close()

2) 兼容性:

image.png

3) 优点:

原生接口,可直接使用;接口简单,使用方便。

4) 缺点:

由兼容性可知,BroadcastChannel 接口不被 IE 和 Safari 支持,如果需要使用 IE 和 Safari,这个接口就不能用了。

2. WebSocket

WebSocket 方案需要后端配合,页面创建 socket 与后端通信,借此实现标签页之间的通信。具体实现不在此赘述。

缺点:

需要后端配合,不够便捷。

3. SharedWorker

引 MDN:

SharedWorker **接口代表一种特定类型的 worker,可以从几个浏览上下文中访问,例如几个窗口、iframe 或其他 worker。它们实现一个不同于普通 worker 的接口,具有不同的全局作用域, SharedWorkerGlobalScope (en-US) 。

注意:如果要使 SharedWorker 连接到多个不同的页面,这些页面必须是同源的(相同的协议、host 以及端口)。

1) 用法:

let worker = new SharedWorker('sharedworkers.js');

// 传递strat指令, 启用端口
worker.port.postMessage('start');

// 接收子线程的数据 
worker.port.onmessage = function (val) { 
    timeDom.innerHTML = val.data 
}

// sharedworkers.js
let a = 0;
onconnect = function (e) { 
    // 通过 e.ports 拿到 port 
    var port = e.ports[0]; 
    // port.onmessage 监听父线程的消息 
    port.onmessage = function () { 
        // port.postMessage 向父线程传递消息 
        port.postMessage(a++) 
    } 
}

2) 兼容性:

image.png

3) 优点:

Worker 运行于独立的线程,对页面性能比较友好,可以执行运算量比较大的计算。

4) 缺点:

需要单独的 worker 文件;调试比页面上的 js 麻烦;兼容性差,IE不支持,Safari 曾经支持过,未来也可能支持,但是现在不支持,不可靠。

4. window.postMessage

引 MDN:

window.postMessage() 方法可以安全地实现跨源通信。通常,对于两个不同页面的脚本,只有当执行它们的页面位于具有相同的协议(通常为https),端口号(443为https的默认值),以及主机  (两个页面的模数 Document.domain设置为相同的值) 时,这两个脚本才能相互通信。window.postMessage() 方法提供了一种受控机制来规避此限制,只要正确的使用,这种方法就很安全。

从广义上讲,一个窗口可以获得对另一个窗口的引用(比如 targetWindow = window.opener),然后在窗口上调用 targetWindow.postMessage() 方法分发一个  MessageEvent 消息。接收消息的窗口可以根据需要自由处理此事件 (en-US)。传递给 window.postMessage() 的参数(比如 message )将 通过消息事件对象暴露给接收消息的窗口

1) 用法:

发送方:

/*
 * A窗口的域名是<http://example.com:8080>,以下是A窗口的script标签下的代码:
 */

var popup = window.open(...popup details...);

// 如果弹出框没有被阻止且加载完成

// 这行语句没有发送信息出去,即使假设当前页面没有改变location(因为targetOrigin设置不对)
popup.postMessage("The user is 'bob' and the password is 'secret'",
                  "https://secure.example.net");

// 假设当前页面没有改变location,这条语句会成功添加message到发送队列中去(targetOrigin设置对了)
popup.postMessage("hello there!", "http://example.org");

function receiveMessage(event)
{
  // 我们能相信信息的发送者吗?  (也许这个发送者和我们最初打开的不是同一个页面).
  if (event.origin !== "http://example.org")
    return;

  // event.source 是我们通过window.open打开的弹出页面 popup
  // event.data 是 popup发送给当前页面的消息 "hi there yourself!  the secret response is: rheeeeet!"
}
window.addEventListener("message", receiveMessage, false);

接收方:

/*
 * 弹出页 popup 域名是<http://example.org>,以下是script标签中的代码:
 */

//当A页面postMessage被调用后,这个function被addEventListener调用
function receiveMessage(event)
{
  // 我们能信任信息来源吗?
  if (event.origin !== "http://example.com:8080")
    return;

  // event.source 就当前弹出页的来源页面
  // event.data 是 "hello there!"

  // 假设你已经验证了所受到信息的origin (任何时候你都应该这样做), 一个很方便的方式就是把event.source
  // 作为回信的对象,并且把event.origin作为targetOrigin
  event.source.postMessage("hi there yourself!  the secret response " +
                           "is: rheeeeet!",
                           event.origin);
}

window.addEventListener("message", receiveMessage, false);

2) 兼容性:

image.png

3) 优点:

兼容性好,安全。

4) 缺点:

需要拿到其他窗口的句柄,一般是 open、opener、iframe,其他情况无法使用。

5. localStorage

localStorage 可以在同源的窗口间共用,js 提供了监听 localStorage 变化的接口,以localStorage 为管道,可以实现标签间的通信。

1) 用法:

发送方:

localStorage.setItem('a', '111')

接收方:

window.onstorage = (e) => {console.log(e)} 
// 或者这样 
window.addEventListener('storage', (e) => console.log(e))

2) 兼容性:

image.png

3) 优点:

兼容性好。

4) 缺点:

间接通过数据变化获取信息,不够直接,用起来比较麻烦。

二. 共享 Session

需求:

多标签页共享登录凭证,关闭所有标签页后,清除登录凭证。

sessionStorage

浏览器自带的 sessionStorage 可以实现关闭窗口时清空凭证,但是无法实现多标签间的共享。

SharedSession

基于 sessionStorage 和上文提到的 localStorage 实现多标签通信,笔者实现了多标签间的共享 session 机制。

用法:

npm i shared-session
#or
yarn add shared-session
import SharedSession from 'shared-session'

//监听数据变化
SharedSession.listen((data) => {
    console.log(data)
})

//获取数据
SharedSession.getItem('key').then((value) => {
    console.log(value)
})

//更新数据
SharedSession.setItem('key', 'value')

//删除某个字段
SharedSession.removeItem('key')

//清除所有数据
SharedSession.clear()

示例 spade5.github.io/multi-tabs-…

仓库 github.com/spade5/mult…