跳到主要内容

1 篇博文 含有标签「shared worker」

查看所有标签

· 阅读需 9 分钟

源 origin = 协议 scheme + 主机名/域名 host + 端口 port

BroadcastChannel:用于同源不同浏览上下文之间进行一对多的定向消息广播。new 一个 bc 实例,设置频道,通过 bc.postMessage 广播消息,其他标签通过监听同频道实例的 message 事件来获取

LocalStorage:通过监听 storage 事件来实现,其他页签改动了 ls 会触发

postMessage:用于不同浏览上下文之间进行一对一的定向消息传递,可以是同源或跨域

MessageChannel:MessageChannel API 可以看作是 postMessage API 的一种更灵活的实现方式,它允许创建独立的双向通信通道,而不需要依赖于 window 或 worker 对象。new 一个 mc 实例,实例具有两个 port,可以通过 port.postMessage 一对一发消息,通过 port.onmessage 监听其他 port 发送的消息。React 调度器就是使用这个 API 使内部的更新与渲染任务参与到浏览器的事件循环的

Shared Worker:传入worker 脚本的 url 以及 name 标识,相同的标识会共享同一个 SharedWorker 后台线程。通过 onconect 事件参数 e.ports[0] 获取页面连接的 port,与 MessageChannel 类似,通过 sw.port.postMessage 广播消息,页面通过 sw.port.onmessage 事件监听消息

1 BroadcastChannel

Broadcast Channel 是一个较新的 Web API,用于在不同的浏览器窗口、标签页或框架之间实现跨窗口通信。它基于发布-订阅模式,允许一个窗口发送消息,并由其他窗口接收。

前提:同源,频道名字一致

  • tab1 使用实例的 postMessage 发送消息
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document1</title>
</head>
<body>
<button>点我发送消息</button>
</body>
<script>
const channel1 = new BroadcastChannel('111')
const btn = document.querySelector('button')
btn.onclick = () => {
channel1.postMessage({data: 'message from tab1'})
console.log('发送成功')
}
</script>
</html>
  • tab2 监听实例的 message 事件,接收消息
const c1 = new BroadcastChannel('111')
c1.addEventListener('message', (e) => {
console.log(e.data)
})
  • 注意在合适的时间调用 channel.close() 断开频道连接

2 LocalStorage

前提:同源

  • tab1 修改 LocalStorage
const btn = document.querySelector('button')
btn.onclick = () => {
localStorage.setItem('text', 'message from tab1')
console.log('修改成功')
}
  • tab2 监听 storage 事件,即监听 LocalStorage 变动
window.addEventListener('storage', (event) => {
let { key, oldValue, newValue } = event
console.log('receive message from ls', key, oldValue, newValue)
});

相似的还有 SessionStorage 与 IndexDB

3 postMessage

前提:主窗口和弹出的新窗口之间(同源)

也可以用于不同页签

  • tab1 打开新标签页后,通过调用新标签页实例的 postMessage 方法向新标签页发送消息
const btn1 = document.querySelector('#open')
const btn2 = document.querySelector('#message')
let newTab = null
btn1.onclick = () => {
// window.open(url, target上下文名称, windowFeatures窗口特性列表)
newTab = window.open('2.html', "123", "height=600,width=600,top=20")
}
btn2.onclick = () => {
const data = { value: 'message from tab1 ' }
newTab?.postMessage(data, "*")
}
  • tab2 监听 message 事件即可
window.addEventListener('message', (e) => {
// 注意屏蔽插件或其他程序的 message 信息
// 屏蔽 react-devtools-content-script 的消息
if(e?.data?.source === 'react-devtools-content-script') {
return
}
console.log(e.data)
})

4 Shared Worker

SharedWorker API 是 HTML5 中提供的一种多线程解决方案,它可以在多个浏览器 TAB 页面之间共享一个后台线程,从而实现跨页面通信。

初始化 shared worker

实例化 Worker ,传入worker 脚本的 url 以及 name 标识,相同的标识会共享同一个 SharedWorker

// html
const sw = new SharedWorker('./sharedWorker.js', 'testWorker')

Shared Worker 实例存在一个 port 属性,相当于当前连接的 tab 页面,用于与共享线程通信

页面监听消息

页面中使用 port.onmessage 监听共享线程传递过来的消息

// html
sw.port.onmessage = (e) => alert(e.data)

页面发送消息

页面中使用 port.postMessage() 向共享线程发送消息

// html
sw.port.postMessage({ tag: 'close' })

worker 处理与分发消息

使用 onconnectself.onconnect 监听页面的连接,通过事件参数 e.port[0] 获取与连接事件关联的第一个 MessagePort 对象,即当前连接的页面

使用 port.onmessage 接收并处理页面传递过来的消息;使用 port.postMessage 向页面发消息

// sharedWorker.js
onconnect = e => {
const port = e.ports[0]
// 可以将 port 缓存起来,用于后续通信
!portsPool.includes(port) && portsPool.push(port)
port.onmessage = (e) => {
console.log('message from tab', e.data)
}
port.postMessage(xxx)
}

调试 shared worker

因为 SharedWorker 的作用域中没有 window 对象,所以 consolealert 等方法都是无法使用的

如果我们需要调试 SharedWorker,可以在浏览器地址栏中输入 chrome://inspect/#workers,这样就可以看到当前页面中的SharedWorker

关闭 shared worker

页面断开链接,通知 worker 关闭;页面关闭时,中断连接

// html
sw.port.postMessage({ tag: 'close' });
sw.port.close();
// 或者
window.onbeforeunload = () => {
sw.port.postMessage({ tag: 'close' });
sw.port.close();
};

worker 删除内部缓存即可

// sharedWorker.js
const index = portsPool.findIndex(item => item === port);
if (~index) {
portsPool.splice(index, 1);
}

当所有创建 SharedWorker 的页面关闭之后,那么 SharedWorker 的生命就走到了尽头,否则它就会一直常驻。

实战:广播与指定页面发送消息

由于 port 没有标识,各标签页之间也无法直接实现精准通信

所以只能通过广播的方式,各标签页通过的 message 的某个字段来区分是否是发给自己的消息

<!-- 1.html -->
<body>
<div>广播消息</div>
<hr />
<div id="broadcast_info"></div>
<button id="broadcast_btn">广播消息</button><br />
<button id="send">向tab2发送消息</button>
<script>
const broadcastBtn = document.querySelector('#broadcast_btn')
const sendBtn = document.querySelector('#send')
const broadcastInfo = document.querySelector('#broadcast_info')
// 初始化
const sw = new SharedWorker('./sharedWorker.js', 'test worker')

sw.port.onmessage = (e) => {
if (e.data.tag === 'broadcast') {
broadcastInfo.innerHTML = e.data.info
}
}

broadcastBtn.addEventListener('click', e => {
sw.port.postMessage({ tag: 'broadcast', info: '来自 tab1 的广播' })
})
sendBtn.addEventListener('click', e => {
sw.port.postMessage({ tag: 'tab2', info: 'tab1 发送给 tab2 的消息' })
})

window.onbeforeunload = () => {
// 取消该port在共享线程中的存储[广播用的]
sw.port.postMessage({ tag: 'close' });
// 关闭与共享线程的连接
sw.port.close();
};
</script>
</body>
<!-- 2.html -->
<body>
<div>广播消息</div>
<hr />
<div id="broadcast_info"></div>
<div>来自其他 tab 的消息</div>
<div id="message"></div>
<button id="broadcast_btn">广播消息</button><br />
<script>
const broadcastBtn = document.querySelector('#broadcast_btn')
const broadcastInfo = document.querySelector('#broadcast_info')
const messageInfo = document.querySelector('#message')
// 初始化
const sw = new SharedWorker('./sharedWorker.js', 'test worker')

sw.port.onmessage = (e) => {
if (e.data.tag === 'broadcast') {
broadcastInfo.innerHTML = e.data.info
}
if(e.data.tag === 'tab2') {
messageInfo.innerHTML = e.data.info
}
}

broadcastBtn.addEventListener('click', e => {
sw.port.postMessage({ tag: 'broadcast', info: '来自 tab2 的广播' })
})

window.onbeforeunload = () => {
// 取消该port在共享线程中的存储[广播用的]
sw.port.postMessage({ tag: 'close' });
// 关闭与共享线程的连接
sw.port.close();
};
</script>
</body>
// sharedWorker.js
const portsPool = []

// { tag: 'tab1' | 'tab2' | 'broadcast' | 'close', info: 'xxx' }
onconnect = e => {
const port = e.ports[0]
!portsPool.includes(port) && portsPool.push(port)
port.onmessage = (e) => {
if (e.data.tag === 'close') {
const index = portsPool.findIndex(item => item === port);
if (~index) {
portsPool.splice(index, 1);
}
} else {
portsPool.forEach(port => {
port.postMessage(e.data)
})
}
}
}

5 MessageChannel

const channel = new MessageChannel();
const output = document.querySelector(".output");
const iframe = document.querySelector("iframe");

iframe.addEventListener("load", onLoad);

function onLoad() {
// 监听 port1 的 message 事件
channel.port1.onmessage = onMessage;

// 通过 postMessage 向 port2 发送消息
iframe.contentWindow.postMessage("Hello from the main page!", "*", [
channel.port2,
]);
}

// Handle messages received on port1
function onMessage(e) {
output.innerHTML = e.data;
}

6 非同源

借助服务,例如 WebSocket