语法
sharedWorker 也是一种 worker,sharedWorker 的创建和 webWorker 的创建一样,使用 new
关键字来进行初始化(不能通过代码片段创建),不同的是可以在浏览器不同标签下(同源,后面的跨页面等均是在同源的前提下)使用同一个js url 创建sharedWorker 会复用同一个实例。
PS:webpack 环境下需要使用相关的 loader 加载,vite 环境直接在导入文件时添加 query 参数 import SharedWorker from './worker.js?sharedworker'
const worker = new SharedWorker('./worker.js'); worker.port.onmessage = (e) => { if (e.data.event === 'connected') { worker.port.postMessage({ event: 'init', payload: { username: 'king' } }) } } 复制代码
基于 sharedWorker 的这种特性,我们可以利用它来进行不同页面之间的通信,具体案例我们最后再说,先来看一下 sharedWorker 的语法。
我们所有的操作都将基于 sharedWorker 的 port 属性,这是一个 MessagePort 对象。
我们可以在 port 上执行以下操作:
- start:激活端口,开始发送消息
- close:断开连接
- postMessage:发送消息,并且可以选择是否将对象的所有权转交
我们可以基于 port 的 message 和 messageerror 事件来监听消息。在绑定 message 事件的时候我们可以使用句柄的方式(onmessage)绑定,也可以使用时间监听器(addEventListener)来绑定,二者的区别在于通过句柄绑定的时候不用显式调用 start。
主线程和 worker 的区别就在于获取 port 的方式不同,在 sharedWorker 内部,我们同样基于 port 进行操作,我们可以通过 onconnect 连接到上述相同的port
/* 连接事件触发 */ self.onconnect = function (e) { console.log("worker内:连接事件触发"); const port = e.ports[0]; port.postMessage('connected') port.onmessage = (e) => { if(e.data.event === 'init') { // exec some code } } } 复制代码
在 sharedWorker 内部同样遵循 webWorker 的一些限制,如 alert ,在 worker 中调用 alert 之后脚本调试器会看到报错
调试
sharedWorker 的调试不能直接在页面的控制台中进行,需要使用浏览器提供的工具进行调试,例如在 chrome 中,可以进入 chrome://inspect/#workers
来管理已经激活的 sharedWorker 实例
点击 inspect 可以打开指定 sharedWorker 的调试工具
点击terminate 可以销毁指定的 sharedWorker
应用
sharedWorker 的应用场景主要是跨页面的通信,例如在不同标签之间同步状态,以消息通知为例,我们在 sharedWorker 中使用 socketio-client 连接消息服务器处理消息逻辑,在 connect 时缓所有的 port,在收到消息或者某个 port 已读消息时,将事件广播到每个端口触发不同的页面事件处理,达到不同页面之间状态同步的目的。
/* 存下与 shared worker 连接了的所有端口 */ const portPool: MessagePort[] = []; let socket: Socket | undefined; const _self: SharedWorkerGlobalScope = self as any; /* 连接事件触发 */ _self.onconnect = function (e: MessageEvent) { // 初始化 socketIO 实例 const port = e.ports[0]; // 将 port 添加到 portPool 中 portPool.push(port); port.postMessage({ event: "connected" }); port.onmessage = (e: MessageEvent<{ event: string; payload: unknown }>) => { switch (e.data.event) { case "init": if (!socket) { socket = io(import.meta.env.VITE_SOCKET_NOTICE, { query: { token: e.data.payload }, }); socket.on("clientNotice", (data) => { // console.log("[ data ] >", data); broadcast({ event: "newNotice", payload: data }); }); } else { console.log("已存在 socket 实例"); } break; case "close": { const index = portPool.findIndex((p) => p === port); // 关闭连接,移除对应 port portPool.splice(index, 1); port.close(); break; } default: broadcast(e.data); } }; }; /* 向当前所有连接了的 port 广播消息 */ function broadcast(message: { event: string; payload: unknown }) { portPool.forEach((port) => { port.postMessage(message); }); }