SharedWorker
是一种允许多个浏览器上下文(如标签页、iframe)共享一个 Worker 实例的技术,能够实现跨多个上下文的通讯和数据共享。通过 SharedWorker
,我们可以在同一来源(同一协议、主机名和端口号)下的不同浏览器上下文之间进行高效的通讯。
SharedWorker 的基本概念
- SharedWorker 实例:多个上下文共享一个 Worker 实例,所有上下文可以通过这个 Worker 实例进行通讯。
- 连接和通讯:每个上下文通过
port
对象与 SharedWorker 通讯,port
是MessagePort
的实例。 - 消息传递:通过
postMessage
方法发送消息,通过onmessage
事件处理器接收消息。
基本用法
1. 创建和连接 SharedWorker
在 JavaScript 文件中定义 SharedWorker 脚本。
// sharedWorker.js
self.onconnect = function(event) {
const port = event.ports[0];
port.onmessage = function(event) {
// 处理来自页面的消息
console.log('Received from page:', event.data);
// 可以将消息发送给其他连接的端口
port.postMessage('Reply from SharedWorker');
};
};
在 HTML 页面中创建并连接 SharedWorker。
// 页面 A 和页面 B 中的代码
const sharedWorker = new SharedWorker('sharedWorker.js');
const port = sharedWorker.port;
// 发送消息
port.postMessage('Hello from Page');
// 接收消息
port.onmessage = (event) => {
console.log('Received from SharedWorker:', event.data);
};
工作流程图
以下是 SharedWorker 实现不同标签页之间通讯的工作流程图:
┌──────────────────────┐ ┌──────────────────────┐
│ Page A │ │ Page B │
│ │ │ │
│ SharedWorker │ │ SharedWorker │
│ 'sharedWorker.js' │ │ 'sharedWorker.js' │
│ │ │ │ │ │
│ ▼ │ │ ▼ │
│ port.postMessage │ │ port.postMessage │
│ ('Hello from A') │ │ ('Hello from B') │
│ │ │ │ │ │
│ ▼ │ │ ▼ │
│ port.onmessage │ │ port.onmessage │
│ event.data === │ │ event.data === │
│ 'Reply from Worker' │ │ 'Reply from Worker' │
└──────────────────────┘ └──────────────────────┘
示例代码
SharedWorker 脚本
sharedWorker.js
:
let connections = [];
self.onconnect = function(event) {
const port = event.ports[0];
connections.push(port);
port.onmessage = function(event) {
console.log('Received from page:', event.data);
// 广播消息给所有连接的端口
connections.forEach(conn => {
if (conn !== port) {
conn.postMessage(event.data);
}
});
};
port.start();
};
标签页 A 的代码
<!DOCTYPE html>
<html>
<head>
<title>Page A</title>
</head>
<body>
<button onclick="sendMessage()">Send Message</button>
<script>
const sharedWorker = new SharedWorker('sharedWorker.js');
const port = sharedWorker.port;
port.start();
function sendMessage() {
port.postMessage('Hello from Page A');
}
port.onmessage = (event) => {
console.log('Received from SharedWorker:', event.data);
};
</script>
</body>
</html>
标签页 B 的代码
<!DOCTYPE html>
<html>
<head>
<title>Page B</title>
</head>
<body>
<script>
const sharedWorker = new SharedWorker('sharedWorker.js');
const port = sharedWorker.port;
port.start();
port.onmessage = (event) => {
console.log('Received from SharedWorker:', event.data);
};
</script>
</body>
</html>
SharedWorker 的特点和优缺点
特点
- 共享实例:多个上下文共享一个 Worker 实例,节省资源。
- 双向通讯:通过
MessagePort
实现双向通讯。 - 同源策略:确保安全性,只有同源的上下文才能相互通讯。
优点
- 高效资源利用:共享一个 Worker 实例,减少内存和 CPU 占用。
- 灵活的消息处理:可以在 Worker 中集中处理和分发消息,适用于复杂的通讯需求。
- 持久连接:连接建立后可以长时间保持,不需要频繁建立和销毁连接。
缺点
- 实现复杂:相比
BroadcastChannel
,实现起来稍微复杂一些。 - 同源限制:只能在同源上下文之间通讯,不同来源无法相互通讯。
- 浏览器支持:部分旧版浏览器可能不支持
SharedWorker
。
实际应用场景
1. 用户登录状态同步
当用户在一个标签页中登录或注销,其他打开同一应用的标签页需要同步登录状态。
// SharedWorker 脚本
self.onconnect = function(event) {
const port = event.ports[0];
port.onmessage = function(event) {
if (event.data === 'User logged in') {
port.postMessage('User logged in');
} else if (event.data === 'User logged out') {
port.postMessage('User logged out');
}
};
port.start();
};
// 标签页中使用
const sharedWorker = new SharedWorker('sharedWorker.js');
const port = sharedWorker.port;
port.start();
port.postMessage('User logged in');
port.onmessage = (event) => {
if (event.data === 'User logged in') {
// 同步用户登录状态
} else if (event.data === 'User logged out') {
// 同步用户注销状态
}
};
2. 实时数据更新
在一个标签页中更新数据,其他标签页需要实时显示更新结果,例如股票行情、实时聊天消息等。
// SharedWorker 脚本
let connections = [];
self.onconnect = function(event) {
const port = event.ports[0];
connections.push(port);
port.onmessage = function(event) {
connections.forEach(conn => {
if (conn !== port) {
conn.postMessage(event.data);
}
});
};
port.start();
};
// 标签页中使用
const sharedWorker = new SharedWorker('sharedWorker.js');
const port = sharedWorker.port;
port.start();
port.postMessage('New data available');
port.onmessage = (event) => {
console.log('Received data update:', event.data);
// 更新界面显示
};
3. 多标签页协同工作
用户在多个标签页中打开同一个应用,需要各标签页协同工作。例如,在一个标签页中填写表单,另一个标签页显示表单预览。
// SharedWorker 脚本
let connections = [];
self.onconnect = function(event) {
const port = event.ports[0];
connections.push(port);
port.onmessage = function(event) {
connections.forEach(conn => {
if (conn !== port) {
conn.postMessage(event.data);
}
});
};
port.start();
};
// 标签页中使用
const sharedWorker = new SharedWorker('sharedWorker.js');
const port = sharedWorker.port;
port.start();
document.getElementById('formInput').addEventListener('input', (event) => {
port.postMessage({ type: 'form_update', value: event.target.value });
});
port.onmessage = (event) => {
if (event.data.type === 'form_update') {
document.getElementById('formPreview').innerText = event.data.value;
}
};
总结
SharedWorker
提供了一种强大的方法,用于在同源的不同浏览器上下文之间进行高效的通讯和数据共享。它特别适合需要共享资源和长时间保持连接的应用场景,如用户登录状态同步、实时数据更新和多标签页协同工作。通过掌握 SharedWorker
的使用方法,开发者可以大大提升 web 应用的用户体验和功能。