小春日和の秘密基地

WebWorker与主线程的三种通信方式

watch_later2021年03月10日
menu_book总字数:924
access_alarm预计阅读时间:13分钟
local_offerJavaScriptlocal_offer前端

图片来源:忘了在哪弄的了,有知道出处的大佬告诉我下_(:з」∠)_

Worker.postMessage

这种方式可以说是最为“正统”的WebWorker与主线程的通信方式,可以进行一对一,一对多的通信。

WebWorker

// main.js
const worker = new Worker('worker.js')

worker.addEventListener('message', e => {
  const { type, data } = e.data
  if (type === 'print') console.log(data)   // 打印出:hello world
})

worker.postMessage({ type: 'welcome' })

// worker.js
self.addEventListener('message', e => {
  const { type } = e.data
  if (type === 'welcome') self.postMessage({ type: 'print', data: 'hello world' })
})

注意,ServiceWorker的写法和普通WebWorker不同:

ServiceWorker

// main.js
const worker = await registerWorker('worker.js')

async function registerWorker(workerUrl) {
  const serviceWorkerRegistration = await navigator.serviceWorker.register(workerUrl)
  await navigator.serviceWorker.ready
  return serviceWorkerRegistration.active
}

// 注意这里用的不是worker
navigator.serviceWorker.addEventListener('message', e => {
  const { type, data } = e.data
  if (type === 'print') console.log(data)   // 打印出:hello world
})

// worker.js
self.addEventListener('message', e => {
  const { type } = e.data
  if (type === 'welcome') {
    // 注意这里的写法
    const windowClients = await clients.matchAll()  // 先拿到ServiceWorker的所有客户端窗口

    // 这会给所有当前ServiceWorker服务的客户端窗口发送消息(一对多)
    windowClients.forEach(windowClient => windowClient.postMessage({
      type: 'print',
      data: 'hello world'
    }))

    // 通过判断url得到特定客户端窗口,只给一个窗口发送信息(一对一)
    windowClients.find(windowClient => windowClient.url === 'xxx').postMessage({})
  }
})

取消事件监听的方式和DOM元素removeEventListener方法相同。

BroadcastChannel

这是三种方式中使用最为简单的方式,不仅用于主线程和WebWorker通信,也可以用于同源窗口之间的通信。只支持多对多,不能指定窗口(当然你可以自己为窗口分配id实现与指定窗口的通信)。

// main.js
const bc = new BroadcastChannel('testChannel')

bc.postMessage('hello world')

// worker.js
const bc = new BroadcastChannel('testChannel')

// 打印出hello world
bc.addEventListener('message', e => console.log(e.data))

bc.postMessage('hello worker')  // 同一窗口或WebWorker下不会触发由自己发出的信息,也就是说这里并不会触发上面的事件

MessageChannel

这种方式可以很方便地实现窗口与ServiceWorker一对一的通信。

// main.js
// 将port2传给serviceWorker进行通信
const worker = await registerWorker('worker.js')

const messageChannel = new MessageChannel()

// postMessage的第二个参数为定义要移交控制权的对象,目前ArrayBuffer、MessagePort、ImageBitmap这些对象实例可以移交
// 将messageChannel实例的其中一个端口移交给serviceWorker,实现一对一通信
worker.postMessage({ 
  type: 'initMessageChannel', 
  data: { port: messageChannel.port2 }
}, [messageChannel.port2])

messageChannel.port1.addEventListener('message', e => {
  const { type, data } = e.data
  if (type === 'print') console.log(data)   // 打印出:hello world
})

// 接下来发送信息既可以继续使用worker.postMessage,也可以选择messageChannel.port1.postMessage

// worker.js
const messageChannelPort = null

self.addEventListener('message', e => {
  const { type, data } = e.data
  if (type === 'initMessageChannel') {
    messageChannelPort = data.port
    messageChannelPort.postMessage({ type: 'print', data: 'hello world' })
  }
})

// 还可以声明一个数组保存所有发来的messageChannelPort,实现一对多的通信等等。

总结

这三种方式各有侧重,虽然都是有办法实现一对一,一对多的功能的,但选择合适的通信方式仍然很重要。

参见

版权声明:本文为原创文章,版权归 小春日和 所有

文章链接:https://koharubiyori.github.io/JavaScript/WebWorker与主线程的三种通信方式/

所有原创文章采用 署名-非商业性使用 4.0 国际 (CC BY-NC 4.0)

您可以自由转载和修改,但必须保证在显著位置注明文章来源,且不能用于商业目的。

north