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 中的网络请求自动携带同源 Cookie
从 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。
这通常通过监听页面的 pagehide 或 unload 事件来实现。
优先使用 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
页面信息
location:protocol: host: hostname: origin: pathname: href: document:referrer: navigator:platform: userAgent: