JavaScript内核与高级编程之:`JavaScript`的`SharedWorker`:其在多个标签页间共享线程的通信机制。

各位听众,欢迎来到今天的“线程共享,快乐编程”讲座!

咳咳,今天咱们聊点刺激的——SharedWorker。 各位都知道,Web Worker 就像一个勤劳的小弟,帮你分担主线程的计算压力,让页面不再卡顿。但是,如果你想让多个标签页共享同一个小弟,一起干活,那普通的 Web Worker 就歇菜了。 这时候,SharedWorker 就该闪亮登场了!

一、SharedWorker:共享的苦力,快乐的通信

简单来说,SharedWorker 就是一个可以被多个浏览上下文(例如,多个标签页、iframe)共享的 Web Worker。 就像一个公共的线程池,大家可以往里面扔任务,然后共享结果。

那么,它和普通 Web Worker 有什么不同呢?

特性 Web Worker SharedWorker
共享范围 只能被创建它的页面使用 可以被同源的多个页面共享
通信方式 直接通过 postMessage 通信 需要通过 port 对象进行通信,建立连接后才能通信
生命周期 页面关闭后,Worker 也会关闭 只要有任何一个页面连接着,Worker 就不会关闭
创建方式 new Worker('worker.js') new SharedWorker('shared_worker.js')
应用场景 独立计算任务,不需要跨页面共享数据 多个页面需要共享数据、状态,例如多人协作编辑、实时数据同步

总结:

  • Web Worker:独享的劳动力,干完就走,互不打扰。
  • SharedWorker:共享的劳动力,大家一起用,共享数据。

二、SharedWorker 的基本使用

1. 创建 SharedWorker 文件 (shared_worker.js)

这部分是 SharedWorker 的核心逻辑,相当于“小弟”的大脑。

// shared_worker.js
let connections = 0;

self.addEventListener('connect', (event) => {
  const port = event.ports[0];
  connections++;
  console.log(`新连接!当前连接数:${connections}`);

  port.addEventListener('message', (e) => {
    const data = e.data;
    console.log(`收到消息:${data}`);

    // 处理消息,并将结果返回给所有连接的客户端
    const result = `[SharedWorker] 收到:${data},处理完毕!`;
    port.postMessage(result);
    // 广播消息给所有连接的客户端
    for (let p of self.clients) {
      p.postMessage(result);
    }

  });

  port.start(); // 启动端口,开始接收消息
  port.postMessage(`[SharedWorker] 欢迎连接!`);
});

self.addEventListener('close', (event) => {
  connections--;
  console.log(`连接关闭!当前连接数:${connections}`);
});

代码解释:

  • self.addEventListener('connect', ...):监听客户端的连接请求。
  • event.ports[0]:获取与客户端通信的 MessagePort 对象。
  • port.addEventListener('message', ...):监听客户端发送的消息。
  • port.postMessage(...):向客户端发送消息。
  • port.start():启动端口,开始接收消息 (必须调用)。
  • self.addEventListener('close', ...): 监听连接关闭。

2. 在 HTML 页面中使用 SharedWorker

<!DOCTYPE html>
<html>
<head>
  <title>SharedWorker Example</title>
</head>
<body>
  <h1>SharedWorker Example</h1>
  <button id="sendMessageBtn">发送消息</button>
  <div id="messageArea"></div>

  <script>
    const messageArea = document.getElementById('messageArea');
    const sendMessageBtn = document.getElementById('sendMessageBtn');
    const sharedWorker = new SharedWorker('shared_worker.js');

    // 获取 port 对象
    const port = sharedWorker.port;

    // 监听 SharedWorker 发送的消息
    port.addEventListener('message', (event) => {
      const message = event.data;
      messageArea.innerHTML += `<p>收到消息:${message}</p>`;
    });

    // 启动端口
    port.start();

    // 发送消息给 SharedWorker
    sendMessageBtn.addEventListener('click', () => {
      const message = `来自标签页的消息:${Date.now()}`;
      port.postMessage(message);
      messageArea.innerHTML += `<p>发送消息:${message}</p>`;
    });

    // 处理错误
    sharedWorker.onerror = (error) => {
        console.error('SharedWorker 错误:', error);
    };

    // 可选:在页面unload时关闭连接
    window.addEventListener('unload', () => {
      // sharedWorker.port.close(); //可选,关闭连接
      // sharedWorker.close(); // 这个是关闭 worker的,会停止worker的运行
    });

  </script>
</body>
</html>

代码解释:

  • new SharedWorker('shared_worker.js'):创建 SharedWorker 实例。
  • sharedWorker.port:获取用于通信的 MessagePort 对象。
  • port.addEventListener('message', ...):监听 SharedWorker 发送的消息。
  • port.start():启动端口,开始接收消息 (必须调用)。
  • port.postMessage(...):向 SharedWorker 发送消息。
  • sharedWorker.onerror:监听错误

重要提示:

  • 必须调用 port.start() 才能开始接收消息。
  • SharedWorker 的 URL 必须与创建它的页面同源。
  • 一个 SharedWorker 实例可以被多个页面共享,所以要小心处理并发问题。

3. 多页面测试

将上面的 HTML 代码复制到两个或多个 HTML 文件中,然后在浏览器中打开这些文件。 你会发现,无论哪个页面发送消息,所有页面都会收到消息,说明 SharedWorker 正在多个标签页之间共享数据。

三、SharedWorker 的高级用法

1. 广播消息

SharedWorker 可以向所有连接的客户端广播消息。 这在多人协作编辑、实时数据同步等场景中非常有用。

shared_worker.js 中,可以使用 clients 对象来获取所有连接的客户端,并向它们发送消息。

// shared_worker.js
self.addEventListener('connect', (event) => {
  const port = event.ports[0];

  port.addEventListener('message', (e) => {
    const data = e.data;
    console.log(`收到消息:${data}`);

    // 广播消息给所有连接的客户端
    const message = `[SharedWorker] 广播:${data}`;
    self.clients.matchAll().then(clients => { // 改成matchAll
      clients.forEach(client => {
        client.postMessage(message);
      });
    });

    port.postMessage(`[SharedWorker] 收到:${data},处理完毕!`);
  });

  port.start();
});

代码解释:

  • self.clients.matchAll():获取所有连接的客户端。
  • clients.forEach(client => ...):遍历所有客户端,并向它们发送消息。

2. 共享数据

SharedWorker 可以使用 SharedArrayBufferAtomics API 来在多个页面之间共享数据。 但是,SharedArrayBuffer 需要开启跨域隔离,配置比较复杂,这里不做详细介绍。

3. 复杂的通信模式

除了简单的消息传递,SharedWorker 还可以实现更复杂的通信模式,例如:

  • 请求-响应模式:客户端发送请求,SharedWorker 处理请求并返回响应。
  • 发布-订阅模式:客户端订阅某些主题,SharedWorker 在主题发生变化时向订阅者发送通知。

这些复杂的通信模式可以通过自定义协议来实现。

四、SharedWorker 的应用场景

  • 多人协作编辑:多个用户可以同时编辑同一个文档,SharedWorker 负责同步数据。
  • 实时数据同步:多个页面需要显示相同的数据,SharedWorker 负责从服务器获取数据并同步到所有页面。
  • 游戏:多个玩家可以通过 SharedWorker 共享游戏状态。
  • 缓存SharedWorker 可以作为全局缓存,缓存一些常用的数据,避免重复请求。
  • 后台任务SharedWorker 可以在后台执行一些任务,例如定时任务、数据同步等。

五、SharedWorker 的注意事项

  • 同源策略SharedWorker 的 URL 必须与创建它的页面同源。
  • 并发问题:多个页面可以同时访问 SharedWorker,所以要小心处理并发问题。可以使用锁、信号量等机制来保证数据的一致性。
  • 内存泄漏:如果 SharedWorker 中存在循环引用,可能会导致内存泄漏。
  • 调试:调试 SharedWorker 比较麻烦,可以使用浏览器的开发者工具来调试。
  • 性能SharedWorker 虽然可以提高性能,但是也会增加 CPU 和内存的消耗。要根据实际情况合理使用。
  • 兼容性SharedWorker 的兼容性不是很好,需要考虑兼容性问题。
  • 生命周期管理:需要注意 SharedWorker 的生命周期管理,避免出现意外情况。

六、SharedWorker 的优缺点

优点:

  • 可以被多个页面共享,节省资源。
  • 可以实现跨页面通信,方便数据共享。
  • 可以在后台执行任务,不影响页面性能。

缺点:

  • 并发问题比较复杂,需要小心处理。
  • 调试比较麻烦。
  • 兼容性不是很好。

七、总结

SharedWorker 是一个强大的工具,可以帮助我们构建更复杂、更强大的 Web 应用。 但是,它也需要我们仔细设计和实现,才能发挥其最大的威力。

希望今天的讲座能帮助大家更好地理解 SharedWorker,并在实际项目中灵活运用。

最后,记住一句真理:

线程共享一时爽,并发处理火葬场!

所以,使用 SharedWorker 时,一定要小心并发问题,做好同步和互斥,才能真正享受到线程共享带来的便利。

感谢各位的收听! 我们下次再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注