当前位置 : 首页 » 文章分类 :  开发  »  JavaScript-Worker

JavaScript-Worker

JavaScript Worker SharedWorker BroadcastChannel 学习笔记


Web Worker 工作者线程

Web Worker API
https://developer.mozilla.org/zh-CN/docs/Web/API/Web_Workers_API

JavaScript 在浏览器中运行是​​单线程​​的
Worker 是浏览器提供的、在后台独立运行的 JavaScript 线程​​。由主页面(主脚本)创建和管理,但运行在完全隔离的环境中。

数据隔离:

  • Worker 线程无法直接访问DOM​​,不能操作 document, window 等 DOM 元素
  • Worker 线程有​​自己的独立内存空间(堆栈、全局作用域)
  • Worker 线程不能访问主线程的全局变量或函数
  • 在 Worker 内部,全局对象是 self 或 this,不再是 window

Worker 线程与主线程通信:

  • 通过 ​​postMessage(data)​​ 方法发送数据
  • 通过 ​​onmessage 事件​​处理接收到的数据
  • 传递的数据是​​副本​​(通常是深拷贝),但也支持可转移对象(性能更好)

通过 new Worker('script.js') 将单独的 .js 文件定义为 Worker

主线程 index.html 中创建 worker:

  • 通过 worker.postMessage 给 Worker 发消息
  • 通过 worker.onmessage 接收来自 Worker 的消息

Worker 线程 worker.js 中:

  • 通过 self.onmessage 接收来自主线程的消息
  • 通过 self.postMessage 给主线程发送消息

index.html 实现:

<!-- index.html -->
<button id="calculate">计算</button>
<script>
  const worker = new Worker('worker.js'); // 创建Worker,加载worker.js
  document.getElementById('calculate').addEventListener('click', () => {
    worker.postMessage({ number: 1000000 }); // 发送消息给Worker
  });
  worker.onmessage = (e) => { // 监听Worker发回的消息
    console.log('计算结果:', e.data.result);
  };
  // 监听错误
  worker.onerror = (e) => console.error('Worker错误:', e);
</script>

worker.js 实现:

// worker.js
self.onmessage = (e) => { // 监听主线程发来的消息
  const input = e.data.number;
  let sum = 0;
  // 模拟一个耗时的计算 (比如计算一个大数以内的所有质数)
  for (let i = 0; i < input; i++) {
    // ... 复杂计算 ...
    sum += i; // 简化示例
  }
  // 计算完成,发送结果回主线程
  self.postMessage({ result: sum });
};

MDN 的 Web Worker 示例
https://github.com/mdn/dom-examples/blob/main/web-workers/simple-web-worker/README.md


SharedWorker 多页面共享线程

https://developer.mozilla.org/en-US/docs/Web/API/SharedWorker

SharedWorker​​ 是 JavaScript 中 Web Workers 的一种特殊类型,允许多个浏览器上下文(如不同标签页、窗口 或 iframe)​​共享同一个后台线程​​。它解决了传统 Web Worker 无法跨页面共享的问题。

1、多页面共享​​
同一域名下的不同页面访问相同脚本路径时,共享同一个 Worker 实例。
2、基于端口的通信​
通过 MessagePort 对象建立连接(每个连接对应一个独立的 port)。
3、独立生命周期
Worker 在所有连接关闭后自动终止,不受单个页面关闭影响。
4、线程安全通信​
使用结构化克隆算法传输数据,确保线程安全。


Chrome 查看 SharedWorker 控制台

Chrome 地址栏输入 chrome://inspect 回车,进入开发者工具调试页面
找到 Shared Workers 点击 inspect 即可打开 SharedWorker 控制台


从 SharedWorker 发起的 HTTP/WebSocket 网络请求,会自动携带同源主线程的 Cookie,包括 token 等 HttpOnly Cookie
但是 SharedWorker 线程里是不能直接 读/写 document.cookie 的

而且,在浏览器的 SharedWorker 控制台是看不到 Cookie 的
因为 SharedWorker 里没有 document,也没有可用的 document.cookie,控制台切到 SharedWorker 上下文也读不到任何 Cookie


创建 SharedWorker 实例

https://developer.mozilla.org/en-US/docs/Web/API/SharedWorker/SharedWorker

var myWorker = new SharedWorker(scriptURL);
var myWorker = new SharedWorker(scriptURL, name);
var myWorker = new SharedWorker(scriptURL, options);

参数:
scriptURL 字符串,表示 worker js 脚本的URL,必须同源
name 可选,SharedWorker 名称,可方便调试
options 可选,额外属性

  • type: 字符串,classic 或 module,默认是 classic
  • name: 字符串,用于命名worker,可用于解决同一脚本不同worker实例的区分
  • credentials: 字符串,凭据选项,可取值 ‘omit’, ‘same-origin’, ‘include’,默认值 omit

返回值:返回一个 SharedWorker 对象,包含以下重要属性:

  • port 一个 MessagePort 对象,用于和 worker 进行通信。
  • onerror 错误事件处理器。

不同页面如果使用相同的 scriptURL 和相同的 name(如果提供了),则会共享同一个 worker 实例。如果 name 不同,即使同一个脚本URL也会创建不同的 worker 实例


self.onconnect

在 Worker 线程内部,即 shared.js 脚本内,通过 self.onconnect 处理 主页面/主线程 的连接事件
事件对象为 MessageEvent,其 ports 属性是一个数组,但通常只有一个端口,即事件对象的第一个端口 e.ports[0]
在 worker 线程内部,我们可以通过获取到的端口(port)来设置 onmessage 事件处理函数,进行双向通信。


MessagePort

https://developer.mozilla.org/en-US/docs/Web/API/MessagePort

port.start() 开始接收消息。如果使用 addEventListener 来监听消息,则必须调用 start() 方法。如果使用 onmessage 则不需要。
port.close() 关闭端口,断开与 worker 的连接。
port.onmessage 设置消息事件处理函数(当 worker 通过该端口发送消息时触发)。
port.onmessageerror 处理消息解析错误事件。

port.postMessage(message, transferList) 向 worker 发送消息。

  • message 要传递的数据,可以是任意结构化克隆算法支持的类型。
  • transferList 可选,一个可转移对象的数组,如 ArrayBuffer 等。

SharedWorker 没有 close 事件

MessagePort 对象并没有一个标准的 close 事件

所以下面这种尝试监听 close 事件感知 port 关闭的代码虽然不会报错,但是无法生效,无法感知 port 也就是标签页的关闭。

  port.addEventListener('close', () => {
    removePort(port);
  });

标准的、也是被广泛推荐的 SharedWorker 感知 port 关闭的做法是:由​​标签页(客户端)在关闭前主动通知​​ SharedWorker。
这通常通过监听页面的 pagehideunload 事件来实现。

优先使用 pagehide,避免使用 unload
因为 unload 会破坏浏览器的 bfcache 优化,并且不可靠,现代最佳实践是​​用 pagehide 事件全面替代 unload 事件。


通过 SharedWorker 在多页面间通信示例

SharedWorker 线程脚本 shared.js

// 保存所有页面的通信端口
const connectedPorts = [];

// 处理来自页面的连接请求
self.onconnect = (e) => {
  // 从事件对象获取通信端口
  const port = e.ports[0];
  
  // 添加到连接列表
  connectedPorts.push(port);
  
  // 处理来自页面的消息
  port.onmessage = (e) => {
    const message = e.data;
    
    // 判断消息类型
    if (message.type === 'close') {
      // 如果是关闭消息,移除端口
      removePort(port);
    } else {
      // 其他消息正常广播
      broadcast({
        type: message.type,
        username: message.username,
        text: message.text,
        timestamp: new Date().toISOString()
      });
    }
  };

  // 发送欢迎消息
  port.postMessage({
    type: 'system',
    text: '连接已建立!当前用户数: ' + connectedPorts.length
  });
  
  // 移除原来的错误事件监听(关键修改)
  // port.addEventListener('close', () => {
  //   removePort(port);
  // });
};

// 广播消息给所有连接的页面
function broadcast(message) {
  connectedPorts.forEach(port => {
    port.postMessage(message);
  });
}

// 移除断开连接的端口
function removePort(port) {
  const index = connectedPorts.indexOf(port);
  if (index !== -1) {
    connectedPorts.splice(index, 1);
    
    // 通知所有用户更新在线人数
    broadcast({
      type: 'system',
      text: `用户已断开。当前用户数: ${connectedPorts.length}`
    });
  }
}

主页面/主线程 中的脚本:

<script>
  // 1. 创建 SharedWorker
  const worker = new SharedWorker('shared.js');
  const port = worker.port;
  
  // 2. 启动通信端口
  port.start();
  
  // 3. 接收来自 Worker 的消息
  port.onmessage = (e) => {
    const message = e.data;
    displayMessage(message);
  };
  
  // 4. 发送消息
  document.getElementById('sendBtn').addEventListener('click', () => {
    const username = document.getElementById('username').value;
    const text = document.getElementById('messageInput').value;
    
    if (text.trim()) {
      port.postMessage({
        type: 'message',
        username: username,
        text: text
      });
      
      document.getElementById('messageInput').value = '';
    }
  });
  
  // 5. 显示消息
  function displayMessage(message) {
    const messagesEl = document.getElementById('messages');
    const messageEl = document.createElement('div');
    
    messageEl.className = message.type === 'system' ? 
      'message system-message' : 'message';
    
    messageEl.innerHTML = message.type === 'system' ?
      `<em>${message.text}</em>` :
      `<strong>${message.username}:</strong> ${message.text}`;
    
    messagesEl.appendChild(messageEl);
    messagesEl.scrollTop = messagesEl.scrollHeight;
  }
  
  // 6. 页面关闭时通知 Worker(保持不变,这是正确的)
  window.addEventListener('pagehide', () => {
    port.postMessage({ type: 'close' });
  });
</script>

BroadcastChannel API


下一篇 JavaScript-Jest

阅读
评论
2k
阅读预计8分钟
创建日期 2025-10-19
修改日期 2025-10-19
类别

页面信息

location:
protocol:
host:
hostname:
origin:
pathname:
href:
document:
referrer:
navigator:
platform:
userAgent:

评论