Web Worker
Web Worker 是 HTML5 提供的一种在后台线程中运行脚本的机制。它允许网页脚本在后台线程中运行,而不会阻塞主线程,从而提高网页的性能和响应能力。例如,当一个网页需要进行大量的计算(如复杂的数据加密、图像渲染中的复杂算法等),如果在主线程中进行,会导致页面冻结,用户无法进行其他操作,如滚动页面、点击按钮等。而使用 Web Worker,这些计算任务可以放在后台线程中执行,主线程依然可以响应用户的交互操作。

查看更多相关内容
OffscreenCanvas 如何在 Web Worker 中进行渲染?OffscreenCanvas 是 HTML5 提供的一个功能,允许在 Web Worker 中进行 Canvas 渲染,从而将复杂的图形计算从主线程移到后台线程。
## OffscreenCanvas 的核心概念
### 特点
- 可以在 Worker 中进行 Canvas 绘图操作
- 支持大部分 Canvas 2D API 和 WebGL API
- 通过 `transferControlToOffscreen()` 方法将 Canvas 控制权转移
- 适用于复杂的图形渲染和动画
## 基本使用
### 主线程设置
```javascript
// 获取 Canvas 元素
const canvas = document.getElementById('myCanvas');
// 将 Canvas 控制权转移到 OffscreenCanvas
const offscreen = canvas.transferControlToOffscreen();
// 创建 Worker
const worker = new Worker('canvas-worker.js');
// 将 OffscreenCanvas 发送给 Worker
worker.postMessage({ canvas: offscreen }, [offscreen]);
```
### Worker 中渲染
```javascript
// canvas-worker.js
self.onmessage = function(e) {
const canvas = e.data.canvas;
const ctx = canvas.getContext('2d');
// 在 Worker 中进行绘图
function render() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 绘制图形
ctx.fillStyle = 'blue';
ctx.fillRect(50, 50, 100, 100);
// 继续动画
requestAnimationFrame(render);
}
render();
};
```
## 实际应用场景
### 1. 复杂动画渲染
```javascript
// 主线程
const canvas = document.getElementById('canvas');
const offscreen = canvas.transferControlToOffscreen();
const worker = new Worker('animation-worker.js');
worker.postMessage({ canvas: offscreen }, [offscreen]);
// animation-worker.js
self.onmessage = function(e) {
const canvas = e.data.canvas;
const ctx = canvas.getContext('2d');
let particles = [];
function initParticles() {
for (let i = 0; i < 1000; i++) {
particles.push({
x: Math.random() * canvas.width,
y: Math.random() * canvas.height,
vx: (Math.random() - 0.5) * 2,
vy: (Math.random() - 0.5) * 2,
size: Math.random() * 3 + 1
});
}
}
function updateParticles() {
particles.forEach(p => {
p.x += p.vx;
p.y += p.vy;
if (p.x < 0 || p.x > canvas.width) p.vx *= -1;
if (p.y < 0 || p.y > canvas.height) p.vy *= -1;
});
}
function render() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
particles.forEach(p => {
ctx.beginPath();
ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
ctx.fillStyle = `rgba(100, 150, 255, 0.7)`;
ctx.fill();
});
updateParticles();
requestAnimationFrame(render);
}
initParticles();
render();
};
```
### 2. 图像处理
```javascript
// 主线程
const canvas = document.getElementById('canvas');
const offscreen = canvas.transferControlToOffscreen();
const worker = new Worker('image-worker.js');
// 加载图像
const img = new Image();
img.onload = function() {
worker.postMessage({
canvas: offscreen,
image: img
}, [offscreen]);
};
img.src = 'image.jpg';
// image-worker.js
self.onmessage = function(e) {
const canvas = e.data.canvas;
const ctx = canvas.getContext('2d');
const img = e.data.image;
// 设置 Canvas 大小
canvas.width = img.width;
canvas.height = img.height;
// 绘制原始图像
ctx.drawImage(img, 0, 0);
// 获取图像数据
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
// 图像处理:灰度化
for (let i = 0; i < data.length; i += 4) {
const avg = (data[i] + data[i + 1] + data[i + 2]) / 3;
data[i] = avg; // R
data[i + 1] = avg; // G
data[i + 2] = avg; // B
}
// 放回处理后的图像
ctx.putImageData(imageData, 0, 0);
};
```
### 3. WebGL 渲染
```javascript
// 主线程
const canvas = document.getElementById('glCanvas');
const offscreen = canvas.transferControlToOffscreen();
const worker = new Worker('webgl-worker.js');
worker.postMessage({ canvas: offscreen }, [offscreen]);
// webgl-worker.js
self.onmessage = function(e) {
const canvas = e.data.canvas;
const gl = canvas.getContext('webgl');
// WebGL 初始化代码
const vertexShaderSource = `
attribute vec4 aVertexPosition;
void main() {
gl_Position = aVertexPosition;
}
`;
const fragmentShaderSource = `
void main() {
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
`;
// 编译着色器
const vertexShader = compileShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
const fragmentShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
// 创建程序
const shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);
// 渲染
function render() {
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.useProgram(shaderProgram);
gl.drawArrays(gl.TRIANGLES, 0, 3);
requestAnimationFrame(render);
}
render();
};
function compileShader(gl, type, source) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
return shader;
}
```
## 与主线程交互
### 动态调整 Canvas 大小
```javascript
// 主线程
const canvas = document.getElementById('canvas');
const offscreen = canvas.transferControlToOffscreen();
const worker = new Worker('canvas-worker.js');
worker.postMessage({ canvas: offscreen }, [offscreen]);
// 监听窗口大小变化
window.addEventListener('resize', function() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
worker.postMessage({
type: 'resize',
width: canvas.width,
height: canvas.height
});
});
// canvas-worker.js
self.onmessage = function(e) {
if (e.data.type === 'resize') {
canvas.width = e.data.width;
canvas.height = e.data.height;
}
};
```
### 接收用户输入
```javascript
// 主线程
const canvas = document.getElementById('canvas');
const offscreen = canvas.transferControlToOffscreen();
const worker = new Worker('canvas-worker.js');
worker.postMessage({ canvas: offscreen }, [offscreen]);
// 发送鼠标位置
canvas.addEventListener('mousemove', function(e) {
const rect = canvas.getBoundingClientRect();
worker.postMessage({
type: 'mousemove',
x: e.clientX - rect.left,
y: e.clientY - rect.top
});
});
// canvas-worker.js
let mouseX = 0, mouseY = 0;
self.onmessage = function(e) {
if (e.data.type === 'mousemove') {
mouseX = e.data.x;
mouseY = e.data.y;
}
};
```
## 注意事项
### 1. Canvas 控制权只能转移一次
```javascript
// ❌ 错误:多次转移
const offscreen1 = canvas.transferControlToOffscreen();
const offscreen2 = canvas.transferControlToOffscreen(); // 报错
// ✅ 正确:只转移一次
const offscreen = canvas.transferControlToOffscreen();
```
### 2. OffscreenCanvas 不支持所有 Canvas API
```javascript
// ❌ 不支持
canvas.toDataURL(); // 在 Worker 中不可用
canvas.toBlob(); // 在 Worker 中不可用
// ✅ 使用 ImageBitmap 代替
const bitmap = await createImageBitmap(canvas);
```
### 3. 浏览器兼容性
```javascript
// 检查浏览器支持
if ('transferControlToOffscreen' in HTMLCanvasElement.prototype) {
// 支持 OffscreenCanvas
} else {
// 不支持,使用回退方案
}
```
## 性能优化
### 1. 批量绘制
```javascript
// ❌ 频繁调用绘制方法
for (let i = 0; i < 1000; i++) {
ctx.beginPath();
ctx.arc(particles[i].x, particles[i].y, particles[i].size, 0, Math.PI * 2);
ctx.fill();
}
// ✅ 批量绘制
ctx.beginPath();
for (let i = 0; i < 1000; i++) {
ctx.moveTo(particles[i].x, particles[i].y);
ctx.arc(particles[i].x, particles[i].y, particles[i].size, 0, Math.PI * 2);
}
ctx.fill();
```
### 2. 使用 ImageBitmap
```javascript
// 加载图像为 ImageBitmap
const bitmap = await createImageBitmap(image);
// 在 Worker 中绘制
ctx.drawImage(bitmap, 0, 0);
```
### 3. 降低渲染频率
```javascript
let lastRenderTime = 0;
const targetFPS = 30;
const frameInterval = 1000 / targetFPS;
function render(timestamp) {
if (timestamp - lastRenderTime >= frameInterval) {
// 执行渲染
ctx.clearRect(0, 0, canvas.width, canvas.height);
// ... 绘制代码
lastRenderTime = timestamp;
}
requestAnimationFrame(render);
}
```
## 最佳实践
1. **复杂渲染使用 OffscreenCanvas**:将计算密集型图形渲染移到 Worker
2. **合理控制渲染频率**:避免不必要的重绘
3. **批量处理**:减少绘制调用次数
4. **使用 ImageBitmap**:提高图像加载和渲染性能
5. **检查浏览器兼容性**:提供回退方案
6. **及时释放资源**:使用完毕后清理资源
前端 · 2月21日 15:44
Service Worker 的生命周期是什么,如何实现离线缓存?Service Worker 是一种特殊的 Web Worker,它作为网络代理运行在浏览器和服务器之间,提供离线功能、推送通知和后台同步等能力。
## Service Worker 的核心概念
### 特点
- 独立于页面生命周期运行
- 拦截和处理网络请求
- 必须在 HTTPS 环境下运行(localhost 除外)
- 可以实现离线缓存和资源预加载
- 支持推送通知和后台同步
## 注册 Service Worker
```javascript
// 检查浏览器支持
if ('serviceWorker' in navigator) {
// 注册 Service Worker
navigator.serviceWorker.register('/service-worker.js')
.then(function(registration) {
console.log('Service Worker 注册成功:', registration.scope);
})
.catch(function(error) {
console.log('Service Worker 注册失败:', error);
});
}
```
## Service Worker 生命周期
### 1. Install(安装)
```javascript
// service-worker.js
const CACHE_NAME = 'my-cache-v1';
const urlsToCache = [
'/',
'/styles/main.css',
'/script/main.js',
'/images/logo.png'
];
self.addEventListener('install', function(event) {
// event.waitUntil 延迟安装完成
event.waitUntil(
caches.open(CACHE_NAME)
.then(function(cache) {
console.log('已打开缓存');
return cache.addAll(urlsToCache);
})
);
// 立即激活新的 Service Worker
self.skipWaiting();
});
```
### 2. Activate(激活)
```javascript
self.addEventListener('activate', function(event) {
event.waitUntil(
caches.keys().then(function(cacheNames) {
return Promise.all(
cacheNames.map(function(cacheName) {
// 删除旧版本的缓存
if (cacheName !== CACHE_NAME) {
console.log('删除旧缓存:', cacheName);
return caches.delete(cacheName);
}
})
);
})
);
// 立即控制所有客户端
self.clients.claim();
});
```
### 3. Fetch(拦截请求)
```javascript
self.addEventListener('fetch', function(event) {
event.respondWith(
caches.match(event.request)
.then(function(response) {
// 缓存命中,返回缓存
if (response) {
return response;
}
// 缓存未命中,发起网络请求
return fetch(event.request).then(
function(response) {
// 检查是否为有效响应
if (!response || response.status !== 200 || response.type !== 'basic') {
return response;
}
// 克隆响应(响应流只能使用一次)
const responseToCache = response.clone();
caches.open(CACHE_NAME)
.then(function(cache) {
cache.put(event.request, responseToCache);
});
return response;
}
);
})
);
});
```
## 缓存策略
### 1. Cache First(缓存优先)
```javascript
self.addEventListener('fetch', function(event) {
event.respondWith(
caches.match(event.request)
.then(function(response) {
return response || fetch(event.request);
})
);
});
```
### 2. Network First(网络优先)
```javascript
self.addEventListener('fetch', function(event) {
event.respondWith(
fetch(event.request)
.then(function(response) {
const responseToCache = response.clone();
caches.open(CACHE_NAME).then(function(cache) {
cache.put(event.request, responseToCache);
});
return response;
})
.catch(function() {
return caches.match(event.request);
})
);
});
```
### 3. Network Only(仅网络)
```javascript
self.addEventListener('fetch', function(event) {
event.respondWith(fetch(event.request));
});
```
### 4. Cache Only(仅缓存)
```javascript
self.addEventListener('fetch', function(event) {
event.respondWith(caches.match(event.request));
});
```
### 5. Stale While Revalidate(缓存优先,后台更新)
```javascript
self.addEventListener('fetch', function(event) {
event.respondWith(
caches.match(event.request).then(function(cachedResponse) {
const fetchPromise = fetch(event.request).then(function(networkResponse) {
caches.open(CACHE_NAME).then(function(cache) {
cache.put(event.request, networkResponse.clone());
});
return networkResponse;
});
return cachedResponse || fetchPromise;
})
);
});
```
## 推送通知
### 订阅推送
```javascript
// 主线程
function subscribeUser() {
return navigator.serviceWorker.register('/service-worker.js')
.then(function(registration) {
const subscribeOptions = {
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(
'BEl62iUYgUivxIkv69yViEuiBIa-IbRMhKDbjjVdMlzQJd0_...'
)
};
return registration.pushManager.subscribe(subscribeOptions);
})
.then(function(pushSubscription) {
console.log('已接收推送订阅:', pushSubscription);
return pushSubscription;
});
}
```
### 处理推送消息
```javascript
// service-worker.js
self.addEventListener('push', function(event) {
const options = {
body: event.data ? event.data.text() : '新消息',
icon: '/images/icon.png',
badge: '/images/badge.png',
vibrate: [100, 50, 100],
data: {
dateOfArrival: Date.now(),
primaryKey: 1
}
};
event.waitUntil(
self.registration.showNotification('推送通知', options)
);
});
// 处理通知点击
self.addEventListener('notificationclick', function(event) {
event.notification.close();
event.waitUntil(
clients.openWindow('/')
);
});
```
## 后台同步
### 注册同步事件
```javascript
// 主线程
navigator.serviceWorker.ready.then(function(registration) {
registration.sync.register('sync-messages');
});
```
### 处理同步事件
```javascript
// service-worker.js
self.addEventListener('sync', function(event) {
if (event.tag === 'sync-messages') {
event.waitUntil(syncMessages());
}
});
function syncMessages() {
return fetch('/api/sync-messages', {
method: 'POST',
body: JSON.stringify(getPendingMessages())
});
}
```
## 调试 Service Worker
### Chrome DevTools
1. 打开 Chrome DevTools
2. 切换到 "Application" 面板
3. 左侧选择 "Service Workers"
4. 可以查看状态、更新、注销等操作
### 更新 Service Worker
```javascript
// 手动更新
navigator.serviceWorker.ready.then(function(registration) {
registration.update();
});
// 监听更新
navigator.serviceWorker.addEventListener('controllerchange', function() {
console.log('Service Worker 已更新');
window.location.reload();
});
```
## 最佳实践
1. **HTTPS 要求**:生产环境必须使用 HTTPS
2. **缓存版本管理**:使用版本号管理缓存
3. **错误处理**:添加完善的错误处理
4. **渐进增强**:确保在不支持 Service Worker 的浏览器中也能正常工作
5. **资源清理**:及时清理旧版本的缓存
6. **性能优化**:合理选择缓存策略
7. **安全性**:验证请求来源,防止 CSRF 攻击
服务端 · 2月21日 15:24
SharedWorker 如何实现跨标签页通信?SharedWorker 允许多个浏览器上下文(如不同的标签页、iframe)共享同一个 Worker 实例,实现跨页面通信和状态共享。
## SharedWorker 的核心概念
### 特点
- 多个页面可以连接到同一个 SharedWorker
- 使用 `port` 对象进行通信
- 通过 `onconnect` 事件处理新连接
- 适合需要跨标签页同步的场景
## 基本使用
### 创建 SharedWorker
```javascript
// 主线程代码
const sharedWorker = new SharedWorker('shared-worker.js');
// 启动端口连接
sharedWorker.port.start();
// 发送消息
sharedWorker.port.postMessage('Hello from page 1');
// 接收消息
sharedWorker.port.onmessage = function(event) {
console.log('Received:', event.data);
};
```
### SharedWorker 脚本实现
```javascript
// shared-worker.js
const connections = [];
// 监听新连接
self.onconnect = function(event) {
const port = event.ports[0];
connections.push(port);
// 监听来自该端口的消息
port.onmessage = function(e) {
console.log('Received from port:', e.data);
// 广播消息给所有连接
connections.forEach(conn => {
if (conn !== port) {
conn.postMessage(e.data);
}
});
};
// 启动端口
port.start();
};
```
## 实际应用场景
### 1. 跨标签页状态同步
```javascript
// shared-worker.js
let sharedState = {
user: null,
theme: 'light',
notifications: []
};
self.onconnect = function(event) {
const port = event.ports[0];
// 发送当前状态给新连接
port.postMessage({ type: 'init', state: sharedState });
port.onmessage = function(e) {
if (e.data.type === 'updateState') {
sharedState = { ...sharedState, ...e.data.state };
// 广播状态更新给所有连接
connections.forEach(conn => {
conn.postMessage({ type: 'stateUpdated', state: sharedState });
});
}
};
connections.push(port);
port.start();
};
```
### 2. 跨标签页聊天
```javascript
// shared-worker.js
const connections = [];
const messages = [];
self.onconnect = function(event) {
const port = event.ports[0];
connections.push(port);
// 发送历史消息
port.postMessage({ type: 'history', messages });
port.onmessage = function(e) {
const message = {
text: e.data.text,
user: e.data.user,
timestamp: Date.now()
};
messages.push(message);
// 广播新消息给所有连接
connections.forEach(conn => {
conn.postMessage({ type: 'newMessage', message });
});
};
port.start();
};
```
### 3. 共享计数器
```javascript
// shared-worker.js
let counter = 0;
self.onconnect = function(event) {
const port = event.ports[0];
// 发送当前计数
port.postMessage({ type: 'counter', value: counter });
port.onmessage = function(e) {
if (e.data.type === 'increment') {
counter++;
} else if (e.data.type === 'decrement') {
counter--;
} else if (e.data.type === 'reset') {
counter = 0;
}
// 广播更新后的计数
connections.forEach(conn => {
conn.postMessage({ type: 'counter', value: counter });
});
};
connections.push(port);
port.start();
};
```
## 高级用法
### 1. 连接管理
```javascript
// shared-worker.js
const connections = new Map(); // 使用 Map 管理连接
self.onconnect = function(event) {
const port = event.ports[0];
const connectionId = Date.now() + Math.random();
connections.set(connectionId, port);
port.onmessage = function(e) {
if (e.data.type === 'ping') {
port.postMessage({ type: 'pong', id: connectionId });
}
};
port.onclose = function() {
connections.delete(connectionId);
console.log('Connection closed:', connectionId);
};
port.start();
};
```
### 2. 广播和定向消息
```javascript
// shared-worker.js
const connections = new Map();
self.onconnect = function(event) {
const port = event.ports[0];
const connectionId = Date.now().toString();
connections.set(connectionId, port);
port.postMessage({ type: 'connected', id: connectionId });
port.onmessage = function(e) {
if (e.data.type === 'broadcast') {
// 广播给所有连接
connections.forEach((conn, id) => {
conn.postMessage({
type: 'broadcast',
from: connectionId,
message: e.data.message
});
});
} else if (e.data.type === 'sendTo') {
// 定向发送给特定连接
const targetPort = connections.get(e.data.targetId);
if (targetPort) {
targetPort.postMessage({
type: 'private',
from: connectionId,
message: e.data.message
});
}
}
};
port.start();
};
```
### 3. 持久化状态
```javascript
// shared-worker.js
let sharedState = {};
// 从 localStorage 加载初始状态
try {
const savedState = localStorage.getItem('sharedWorkerState');
if (savedState) {
sharedState = JSON.parse(savedState);
}
} catch (e) {
console.error('Failed to load state:', e);
}
function saveState() {
try {
localStorage.setItem('sharedWorkerState', JSON.stringify(sharedState));
} catch (e) {
console.error('Failed to save state:', e);
}
}
self.onconnect = function(event) {
const port = event.ports[0];
port.postMessage({ type: 'init', state: sharedState });
port.onmessage = function(e) {
if (e.data.type === 'update') {
sharedState = { ...sharedState, ...e.data.state };
saveState();
// 广播更新
connections.forEach(conn => {
conn.postMessage({ type: 'stateUpdated', state: sharedState });
});
}
};
connections.push(port);
port.start();
};
```
## 注意事项
### 1. 端口必须启动
```javascript
// ❌ 忘记启动端口
const sharedWorker = new SharedWorker('worker.js');
sharedWorker.port.postMessage('Hello'); // 可能不会发送
// ✅ 正确启动端口
const sharedWorker = new SharedWorker('worker.js');
sharedWorker.port.start();
sharedWorker.port.postMessage('Hello');
```
### 2. 消息是异步的
```javascript
// 消息不会立即到达
sharedWorker.port.postMessage('message1');
sharedWorker.port.postMessage('message2');
// 需要通过 onmessage 接收响应
```
### 3. 连接断开处理
```javascript
// Worker 端
port.onclose = function() {
console.log('Port closed');
// 清理资源
};
// 主线程端
sharedWorker.port.close();
```
## 与 Dedicated Worker 的对比
| 特性 | Dedicated Worker | Shared Worker |
|------|------------------|---------------|
| 作用域 | 单页面 | 多页面共享 |
| 连接数 | 1:1 | 1:N |
| 通信方式 | postMessage | port.postMessage |
| 适用场景 | 单页面后台任务 | 跨页面通信 |
## 最佳实践
1. **连接管理**:使用 Map 或数组管理所有连接
2. **错误处理**:在连接和消息处理中添加错误处理
3. **状态同步**:新连接时发送当前状态
4. **资源清理**:连接断开时清理相关资源
5. **消息格式**:使用统一的消息格式(如 `{ type, data }`)
服务端 · 2月21日 15:24
Web Worker 和 WebAssembly 有什么区别,如何选择?Web Worker 和 WebAssembly (WASM) 都可以用于提升 Web 应用的性能,但它们解决的问题和适用场景有所不同。
## Web Worker 和 WebAssembly 的对比
### Web Worker 的特点
**优势:**
- 在独立线程中运行 JavaScript 代码
- 不阻塞主线程,保持 UI 响应
- 可以访问部分浏览器 API(如 fetch、IndexedDB)
- 易于使用,与现有 JavaScript 代码无缝集成
- 适合处理 I/O 密集型任务
**限制:**
- 仍然是 JavaScript,性能受限于 JS 引擎
- 无法直接访问 DOM
- 消息传递有开销
- 不支持同步操作
### WebAssembly 的特点
**优势:**
- 接近原生代码的执行速度
- 支持多种语言编译(C/C++、Rust、Go 等)
- 二进制格式,体积小,加载快
- 可以与 JavaScript 互操作
- 适合计算密集型任务
**限制:**
- 需要编译步骤
- 不直接访问浏览器 API
- 调试相对困难
- 不适合 I/O 密集型任务
## 使用场景对比
### Web Worker 适用场景
```javascript
// 1. 大数据处理
const worker = new Worker('data-worker.js');
worker.postMessage(largeDataSet);
// 2. 复杂的 DOM 操作(通过消息传递)
worker.postMessage({ type: 'calculateLayout', data });
// 3. 网络请求和数据处理
worker.postMessage({ type: 'fetch', url: '/api/data' });
// 4. 定时任务和后台处理
worker.postMessage({ type: 'schedule', interval: 1000 });
```
### WebAssembly 适用场景
```javascript
// 1. 复杂的数学计算
const wasmModule = await WebAssembly.instantiateStreaming(
fetch('compute.wasm')
);
const result = wasmModule.instance.exports.complexCalculation(data);
// 2. 图像/视频处理
const wasmModule = await WebAssembly.instantiateStreaming(
fetch('image-processor.wasm')
);
const processedImage = wasmModule.instance.exports.processImage(imageData);
// 3. 游戏/3D 渲染
const wasmModule = await WebAssembly.instantiateStreaming(
fetch('game-engine.wasm')
);
wasmModule.instance.exports.renderFrame(frameData);
// 4. 加密/解密
const wasmModule = await WebAssembly.instantiateStreaming(
fetch('crypto.wasm')
);
const encrypted = wasmModule.instance.exports.encrypt(data, key);
```
## 结合使用 Web Worker 和 WebAssembly
### 在 Web Worker 中加载 WebAssembly
```javascript
// 主线程
const worker = new Worker('wasm-worker.js');
worker.postMessage({ action: 'init', wasmUrl: 'compute.wasm' });
worker.onmessage = function(e) {
if (e.data.type === 'ready') {
// WASM 已加载,可以发送计算任务
worker.postMessage({ action: 'compute', data: largeData });
} else if (e.data.type === 'result') {
console.log('计算结果:', e.data.result);
}
};
// wasm-worker.js
let wasmModule = null;
self.onmessage = async function(e) {
if (e.data.action === 'init') {
// 在 Worker 中加载 WASM
wasmModule = await WebAssembly.instantiateStreaming(
fetch(e.data.wasmUrl)
);
self.postMessage({ type: 'ready' });
} else if (e.data.action === 'compute') {
// 使用 WASM 进行计算
const result = wasmModule.instance.exports.compute(e.data.data);
self.postMessage({ type: 'result', result });
}
};
```
### 实际应用示例:图像处理
```javascript
// 主线程
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const offscreen = canvas.transferControlToOffscreen();
const worker = new Worker('image-processor-worker.js');
worker.postMessage({
action: 'init',
canvas: offscreen,
wasmUrl: 'image-processor.wasm'
}, [offscreen]);
// 加载图像
const img = new Image();
img.onload = function() {
ctx.drawImage(img, 0, 0);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
worker.postMessage({ action: 'process', imageData });
};
img.src = 'image.jpg';
// image-processor-worker.js
let wasmModule = null;
let canvas = null;
let ctx = null;
self.onmessage = async function(e) {
if (e.data.action === 'init') {
canvas = e.data.canvas;
ctx = canvas.getContext('2d');
// 加载 WASM 模块
wasmModule = await WebAssembly.instantiateStreaming(
fetch(e.data.wasmUrl)
);
self.postMessage({ type: 'ready' });
} else if (e.data.action === 'process') {
// 使用 WASM 处理图像
const imageData = e.data.imageData;
const processedData = wasmModule.instance.exports.processImage(
imageData.data,
imageData.width,
imageData.height
);
// 将处理后的数据放回 Canvas
const newImageData = new ImageData(
new Uint8ClampedArray(processedData),
imageData.width,
imageData.height
);
ctx.putImageData(newImageData, 0, 0);
}
};
```
## 性能对比
### 计算密集型任务
```javascript
// Web Worker 版本
// worker.js
self.onmessage = function(e) {
const data = e.data;
let result = 0;
for (let i = 0; i < data.length; i++) {
result += Math.sqrt(data[i]);
}
self.postMessage(result);
};
// WebAssembly 版本
// C++ 代码编译成 WASM
extern "C" {
double compute(double* data, int length) {
double result = 0;
for (int i = 0; i < length; i++) {
result += sqrt(data[i]);
}
return result;
}
}
// 性能测试结果(示例)
// Web Worker: ~500ms
// WebAssembly: ~50ms(10倍性能提升)
```
### I/O 密集型任务
```javascript
// Web Worker 版本(适合)
self.onmessage = async function(e) {
const urls = e.data.urls;
const results = [];
for (const url of urls) {
const response = await fetch(url);
const data = await response.json();
results.push(data);
}
self.postMessage(results);
};
// WebAssembly 版本(不适合)
// WASM 无法直接访问 fetch API
// 需要通过 JavaScript 桥接,增加复杂度
```
## 选择建议
### 选择 Web Worker 当:
1. **需要保持 UI 响应**:长时间运行的任务
2. **I/O 密集型任务**:网络请求、文件操作
3. **需要访问浏览器 API**:fetch、IndexedDB、WebSocket
4. **现有 JavaScript 代码**:易于迁移和集成
5. **多线程并行处理**:可以创建多个 Worker 并行工作
```javascript
// 示例:并行处理多个文件
const workers = [];
const files = ['file1.txt', 'file2.txt', 'file3.txt'];
files.forEach(file => {
const worker = new Worker('file-processor.js');
worker.postMessage({ file });
workers.push(worker);
});
```
### 选择 WebAssembly 当:
1. **计算密集型任务**:数学运算、图像处理、加密
2. **需要极致性能**:游戏引擎、3D 渲染、物理模拟
3. **现有 C/C++/Rust 代码**:可以复用现有代码库
4. **需要精确控制内存**:手动内存管理
5. **需要减少包体积**:二进制格式更小
```javascript
// 示例:高性能计算
const wasmModule = await WebAssembly.instantiateStreaming(
fetch('high-performance-compute.wasm')
);
// 调用 WASM 函数
const result = wasmModule.instance.exports.compute(largeData);
```
### 结合使用 Web Worker + WebAssembly 当:
1. **需要高性能计算且不阻塞 UI**:在 Worker 中运行 WASM
2. **复杂的多步骤处理**:Worker 处理 I/O,WASM 处理计算
3. **需要并行处理多个计算任务**:多个 Worker 各自运行 WASM
```javascript
// 示例:高性能并行处理
const workers = [];
const tasks = [data1, data2, data3, data4];
tasks.forEach((task, index) => {
const worker = new Worker('wasm-worker.js');
worker.postMessage({
action: 'compute',
data: task,
wasmUrl: 'compute.wasm'
});
workers.push(worker);
});
```
## 最佳实践
1. **性能分析**:使用性能分析工具确定瓶颈
2. **渐进式优化**:先优化算法,再考虑使用 WASM
3. **合理选择**:根据任务类型选择合适的技术
4. **结合使用**:在 Worker 中使用 WASM 获得最佳性能
5. **测试验证**:对比不同方案的性能表现
6. **考虑兼容性**:检查浏览器对 WASM 的支持情况
服务端 · 2月21日 15:12
Web Worker 和主线程之间如何进行通信?Web Worker 和主线程之间的通信主要通过 postMessage() 方法和 onmessage 事件实现。
## 基本通信机制
### 主线程向 Worker 发送消息
```javascript
const worker = new Worker('worker.js');
// 发送简单数据
worker.postMessage('Hello Worker');
// 发送复杂对象
worker.postMessage({
type: 'compute',
data: [1, 2, 3, 4, 5]
});
// 发送可转移对象(Transferable Objects)
const buffer = new ArrayBuffer(1024);
worker.postMessage({ buffer }, [buffer]);
```
### Worker 向主线程发送消息
```javascript
// worker.js
self.onmessage = function(event) {
const result = processData(event.data);
self.postMessage(result);
};
// 或者使用 addEventListener
self.addEventListener('message', function(event) {
self.postMessage({ status: 'received' });
});
```
## 消息传递特点
### 1. 深拷贝 vs 转移
默认情况下,消息通过**深拷贝**传递:
```javascript
// 深拷贝 - 原数据保留
const data = { value: 42 };
worker.postMessage(data);
console.log(data.value); // 42 - 数据仍然存在
```
使用**Transferable Objects**进行转移:
```javascript
// 转移 - 原数据被清空
const buffer = new ArrayBuffer(1024);
worker.postMessage({ buffer }, [buffer]);
console.log(buffer.byteLength); // 0 - 数据已被转移
```
### 2. 结构化克隆算法
postMessage 使用结构化克隆算法,支持:
- 基本类型(string, number, boolean, null, undefined)
- 对象和数组
- Date, RegExp, Blob, File, ArrayBuffer
- Map, Set, ImageData
不支持:
- 函数
- DOM 节点
- Error 对象
- Symbol
## 双向通信示例
```javascript
// 主线程
const worker = new Worker('worker.js');
worker.onmessage = function(event) {
console.log('Worker says:', event.data);
};
worker.postMessage({ action: 'start' });
// worker.js
self.onmessage = function(event) {
if (event.data.action === 'start') {
// 执行任务
const result = heavyComputation();
self.postMessage({ action: 'complete', result });
}
};
```
## 消息队列
消息是异步的,通过消息队列传递:
```javascript
// 主线程连续发送多条消息
for (let i = 0; i < 5; i++) {
worker.postMessage({ index: i });
}
// Worker 按顺序接收
self.onmessage = function(event) {
console.log('Processing:', event.data.index);
};
```
## 错误处理
```javascript
// 主线程监听 Worker 错误
worker.onerror = function(event) {
console.error('Worker error:', event.message);
console.error('Filename:', event.filename);
console.error('Line:', event.lineno);
};
// Worker 内部错误处理
try {
// 可能出错的代码
} catch (error) {
self.postMessage({ error: error.message });
}
```
## 性能优化建议
1. **使用 Transferable Objects**:对于大数据(ArrayBuffer、Blob),使用转移而非拷贝
2. **批量处理**:减少消息传递次数,合并数据
3. **避免频繁通信**:尽量在 Worker 内部完成更多计算
4. **使用 SharedArrayBuffer**:多个 Worker 共享内存(需要特定头部配置)
服务端 · 2月21日 15:12
什么是 Web Worker,它如何提升网页性能?Web Worker 是 HTML5 提供的一种在后台线程中运行 JavaScript 的机制。它允许在不阻塞主线程的情况下执行耗时任务,从而提升用户体验。
## 核心概念
Web Worker 运行在独立的线程中,与主线程并行执行。这意味着:
- **独立执行环境**:Worker 有自己的全局对象,不是 window,而是 DedicatedWorkerGlobalScope 或 SharedWorkerGlobalScope
- **无 DOM 访问权限**:Worker 无法直接访问 DOM、document、window 等对象
- **消息通信**:通过 postMessage() 和 onmessage 事件与主线程通信
- **同源策略**:Worker 脚本必须与主页面同源
## 基本用法
```javascript
// 主线程创建 Worker
const worker = new Worker('worker.js');
// 发送消息给 Worker
worker.postMessage({ data: 'Hello from main thread' });
// 接收 Worker 的消息
worker.onmessage = function(event) {
console.log('Received from worker:', event.data);
};
// worker.js
self.onmessage = function(event) {
const result = performHeavyComputation(event.data);
self.postMessage(result);
};
```
## 应用场景
1. **大数据处理**:处理大型数组、JSON 数据
2. **复杂计算**:图像处理、加密解密、物理模拟
3. **后台任务**:定期检查、数据同步
4. **实时数据处理**:视频/音频处理、WebSocket 消息处理
## 限制
- 无法访问 DOM
- 无法使用某些 API(如 localStorage)
- 同源限制
- 创建 Worker 有一定的性能开销
服务端 · 2月21日 15:11
Web Worker 的安全性有哪些考虑?Web Worker 的安全性是开发者需要重点关注的问题,特别是在处理敏感数据或跨域请求时。
## 主要安全考虑
### 1. 同源策略(Same-Origin Policy)
Web Worker 遵循同源策略,只能加载与主页面同源的脚本。
```javascript
// ❌ 跨域加载 Worker - 会失败
const worker = new Worker('https://malicious-site.com/worker.js');
// ✅ 同源加载 Worker - 正常工作
const worker = new Worker('/workers/worker.js');
// ✅ 使用 Blob URL 绕过(但内容仍需同源)
const workerCode = fetch('/trusted-source/worker.js')
.then(response => response.text())
.then(code => {
const blob = new Blob([code], { type: 'application/javascript' });
return new Worker(URL.createObjectURL(blob));
});
```
### 2. 数据隔离
Worker 运行在独立的线程中,无法直接访问主线程的变量和 DOM。
```javascript
// 主线程
const secretData = 'sensitive information';
const worker = new Worker('worker.js');
// Worker 无法直接访问 secretData
// worker.js
console.log(secretData); // ReferenceError: secretData is not defined
// ✅ 通过消息传递数据(注意数据会被深拷贝)
worker.postMessage({ data: secretData });
```
### 3. 消息传递安全
使用 `postMessage` 传递数据时,数据会被结构化克隆。
```javascript
// ❌ 不安全的消息传递
worker.postMessage({
password: userPassword,
token: authToken
});
// ✅ 安全的消息传递 - 只传递必要数据
worker.postMessage({
taskId: generateId(),
encryptedData: encryptData(data)
});
// ✅ 使用 Transferable Objects 避免数据拷贝
const buffer = new ArrayBuffer(1024);
worker.postMessage({ buffer }, [buffer]);
// buffer 现在为空,数据已转移
```
### 4. 防止 XSS 攻击
避免在 Worker 中执行不可信的代码。
```javascript
// ❌ 危险:执行用户输入的代码
const userCode = getUserInput();
const blob = new Blob([userCode], { type: 'application/javascript' });
const worker = new Worker(URL.createObjectURL(blob));
// ✅ 安全:使用预定义的 Worker 脚本
const worker = new Worker('/trusted-worker.js');
worker.postMessage({ userInput: getUserInput() });
```
### 5. CORS 配置
如果需要跨域加载 Worker 资源,需要正确配置 CORS。
```javascript
// 服务器端需要设置 CORS 头部
// Access-Control-Allow-Origin: https://your-domain.com
// Access-Control-Allow-Methods: GET, POST
// Access-Control-Allow-Headers: Content-Type
// 客户端
const worker = new Worker('https://api.example.com/worker.js');
```
## 安全最佳实践
### 1. 输入验证
```javascript
// worker.js
self.onmessage = function(e) {
const data = e.data;
// 验证输入数据
if (!isValidInput(data)) {
console.error('Invalid input detected');
return;
}
// 处理数据
const result = processData(data);
self.postMessage(result);
};
function isValidInput(data) {
// 检查数据类型
if (typeof data !== 'object' || data === null) {
return false;
}
// 检查必需字段
if (!data.type || !data.payload) {
return false;
}
// 检查数据大小
if (JSON.stringify(data).length > MAX_SIZE) {
return false;
}
return true;
}
```
### 2. 错误处理
```javascript
// 主线程
const worker = new Worker('worker.js');
worker.onerror = function(event) {
console.error('Worker error:', event.message);
// 记录错误信息(不暴露敏感信息)
logError({
message: sanitizeErrorMessage(event.message),
filename: event.filename,
lineno: event.lineno
});
// 根据错误类型采取适当措施
if (isSecurityError(event.error)) {
terminateWorker();
}
};
function sanitizeErrorMessage(message) {
// 移除可能包含敏感信息的部分
return message.replace(/password|token|secret/gi, '[REDACTED]');
}
```
### 3. 资源限制
```javascript
// worker.js
const MAX_EXECUTION_TIME = 5000; // 5秒
let startTime = null;
self.onmessage = function(e) {
startTime = performance.now();
try {
const result = processWithTimeout(e.data, MAX_EXECUTION_TIME);
self.postMessage(result);
} catch (error) {
if (error instanceof TimeoutError) {
self.postMessage({ error: 'Operation timed out' });
} else {
self.postMessage({ error: 'Processing failed' });
}
}
};
function processWithTimeout(data, timeout) {
const result = [];
for (let i = 0; i < data.length; i++) {
// 检查是否超时
if (performance.now() - startTime > timeout) {
throw new TimeoutError();
}
result.push(processItem(data[i]));
}
return result;
}
```
### 4. 消息加密
```javascript
// 使用 Web Crypto API 加密敏感数据
async function encryptData(data, key) {
const encoder = new TextEncoder();
const encodedData = encoder.encode(data);
const iv = crypto.getRandomValues(new Uint8Array(12));
const encrypted = await crypto.subtle.encrypt(
{
name: 'AES-GCM',
iv: iv
},
key,
encodedData
);
return { encrypted, iv };
}
// 主线程
const key = await crypto.subtle.generateKey(
{ name: 'AES-GCM', length: 256 },
true,
['encrypt', 'decrypt']
);
const encrypted = await encryptData(sensitiveData, key);
worker.postMessage({ encrypted, iv });
```
### 5. Worker 池管理
```javascript
class SecureWorkerPool {
constructor(workerPath, poolSize = 4) {
this.workerPath = workerPath;
this.poolSize = poolSize;
this.workers = [];
this.taskQueue = [];
this.init();
}
init() {
for (let i = 0; i < this.poolSize; i++) {
const worker = new Worker(this.workerPath);
// 添加错误处理
worker.onerror = (event) => {
this.handleWorkerError(event, worker);
};
worker.onmessage = (e) => this.handleMessage(e, worker);
this.workers.push({ worker, busy: false, id: i });
}
}
handleWorkerError(event, workerObj) {
console.error(`Worker ${workerObj.id} error:`, event.message);
// 重启 Worker
this.restartWorker(workerObj);
}
restartWorker(workerObj) {
const oldWorker = workerObj.worker;
oldWorker.terminate();
const newWorker = new Worker(this.workerPath);
newWorker.onerror = (event) => this.handleWorkerError(event, workerObj);
newWorker.onmessage = (e) => this.handleMessage(e, workerObj);
workerObj.worker = newWorker;
workerObj.busy = false;
}
execute(data) {
return new Promise((resolve, reject) => {
// 验证输入
if (!this.validateInput(data)) {
reject(new Error('Invalid input'));
return;
}
const availableWorker = this.workers.find(w => !w.busy);
if (availableWorker) {
availableWorker.busy = true;
availableWorker.worker.postMessage({ data, resolve, reject });
} else {
this.taskQueue.push({ data, resolve, reject });
}
});
}
validateInput(data) {
// 实现输入验证逻辑
return true;
}
}
```
## Content Security Policy (CSP)
配置 CSP 来限制 Worker 的行为:
```html
<!-- 在 HTML 头部添加 CSP -->
<meta http-equiv="Content-Security-Policy"
content="worker-src 'self' https://trusted-domain.com">
```
```javascript
// 检查 Worker 是否受 CSP 限制
if (self.isSecureContext) {
console.log('Worker is running in secure context');
}
```
## 安全检查清单
- [ ] 验证所有输入数据
- [ ] 使用 HTTPS 加载 Worker 脚本
- [ ] 遵循同源策略
- [ ] 避免在 Worker 中执行不可信代码
- [ ] 实施错误处理和日志记录
- [ ] 限制 Worker 执行时间
- [ ] 加密敏感数据
- [ ] 配置适当的 CSP 策略
- [ ] 定期更新 Worker 代码
- [ ] 监控 Worker 行为和性能
## 常见安全漏洞
### 1. 数据泄露
```javascript
// ❌ 泄露敏感信息
worker.postMessage({ password: userPassword });
// ✅ 使用加密
const encrypted = await encryptPassword(userPassword);
worker.postMessage({ encrypted });
```
### 2. 拒绝服务攻击
```javascript
// ❌ 无限制的处理
worker.postMessage(hugeData);
// ✅ 限制数据大小
if (data.length > MAX_SIZE) {
throw new Error('Data too large');
}
```
### 3. 代码注入
```javascript
// ❌ 执行用户代码
eval(userCode);
// ✅ 使用沙箱环境
const sandbox = createSandbox();
sandbox.execute(userCode);
```
## 最佳实践总结
1. **输入验证**:严格验证所有输入数据
2. **错误处理**:完善的错误处理机制
3. **资源限制**:限制执行时间和数据大小
4. **数据加密**:敏感数据使用加密传输
5. **HTTPS**:使用 HTTPS 加载 Worker 脚本
6. **CSP 配置**:配置适当的内容安全策略
7. **定期审计**:定期审查 Worker 代码的安全性
8. **监控日志**:监控 Worker 行为并记录日志
服务端 · 2月21日 15:11
如何优化 Web Worker 的性能?Web Worker 的性能优化是确保应用高效运行的关键。以下是多个方面的优化策略。
## 1. Worker 创建和销毁优化
### 复用 Worker 实例
```javascript
// ❌ 频繁创建和销毁(性能差)
function processTask(data) {
const worker = new Worker('worker.js');
worker.postMessage(data);
worker.onmessage = function(e) {
console.log(e.data);
worker.terminate();
};
}
// ✅ 复用 Worker(性能好)
const worker = new Worker('worker.js');
const pendingTasks = [];
function processTask(data) {
return new Promise((resolve) => {
const taskId = Date.now();
pendingTasks[taskId] = resolve;
worker.postMessage({ taskId, data });
});
}
worker.onmessage = function(e) {
const { taskId, result } = e.data;
if (pendingTasks[taskId]) {
pendingTasks[taskId](result);
delete pendingTasks[taskId];
}
};
```
### Worker 池模式
```javascript
class WorkerPool {
constructor(workerPath, poolSize = 4) {
this.workerPath = workerPath;
this.poolSize = poolSize;
this.workers = [];
this.taskQueue = [];
this.init();
}
init() {
for (let i = 0; i < this.poolSize; i++) {
const worker = new Worker(this.workerPath);
worker.onmessage = (e) => this.handleMessage(e, worker);
this.workers.push({ worker, busy: false });
}
}
execute(data) {
return new Promise((resolve) => {
const availableWorker = this.workers.find(w => !w.busy);
if (availableWorker) {
availableWorker.busy = true;
availableWorker.worker.postMessage({ data, resolve });
} else {
this.taskQueue.push({ data, resolve });
}
});
}
handleMessage(event, workerObj) {
const { result } = event.data;
const pendingTask = workerObj.worker.pendingTask;
if (pendingTask) {
pendingTask.resolve(result);
workerObj.worker.pendingTask = null;
}
workerObj.busy = false;
if (this.taskQueue.length > 0) {
const nextTask = this.taskQueue.shift();
workerObj.busy = true;
workerObj.worker.pendingTask = nextTask;
workerObj.worker.postMessage({ data: nextTask.data });
}
}
}
// 使用 Worker 池
const pool = new WorkerPool('worker.js', 4);
pool.execute(largeData).then(result => console.log(result));
```
## 2. 消息传递优化
### 使用 Transferable Objects
```javascript
// ❌ 深拷贝(性能差)
const buffer = new ArrayBuffer(1024 * 1024);
worker.postMessage({ buffer }); // 拷贝 1MB 数据
// ✅ 转移所有权(性能好)
const buffer = new ArrayBuffer(1024 * 1024);
worker.postMessage({ buffer }, [buffer]); // 零拷贝
// buffer 现在为空,所有权已转移
```
### 批量处理消息
```javascript
// ❌ 频繁发送小消息
for (let i = 0; i < 10000; i++) {
worker.postMessage({ index: i, value: data[i] });
}
// ✅ 批量发送
worker.postMessage({ data: data.slice(0, 10000) });
```
### 使用 SharedArrayBuffer(需要特定头部)
```javascript
// 需要服务器设置 COOP/COEP 头部
// Cross-Origin-Opener-Policy: same-origin
// Cross-Origin-Embedder-Policy: require-corp
const sharedBuffer = new SharedArrayBuffer(1024);
const sharedArray = new Int32Array(sharedBuffer);
worker.postMessage({ sharedBuffer }, [sharedBuffer]);
// 主线程和 Worker 可以同时访问 sharedArray
sharedArray[0] = 42;
```
## 3. 数据处理优化
### 分块处理大数据
```javascript
// worker.js
self.onmessage = function(e) {
const { data, chunkSize } = e.data;
const results = [];
for (let i = 0; i < data.length; i += chunkSize) {
const chunk = data.slice(i, i + chunkSize);
const result = processChunk(chunk);
results.push(result);
// 定期报告进度
if (i % (chunkSize * 10) === 0) {
self.postMessage({ type: 'progress', progress: i / data.length });
}
}
self.postMessage({ type: 'complete', results });
};
```
### 使用 WebAssembly 加速计算
```javascript
// worker.js
const wasmModule = await WebAssembly.instantiateStreaming(
fetch('compute.wasm')
);
self.onmessage = function(e) {
const { data } = e.data;
const result = wasmModule.instance.exports.compute(data);
self.postMessage(result);
};
```
## 4. 内存管理优化
### 及时释放资源
```javascript
// 主线程
const worker = new Worker('worker.js');
// 使用完毕后终止 Worker
worker.terminate();
// Worker 内部
self.onmessage = function(e) {
const result = process(e.data);
self.postMessage(result);
// 清理大对象
e.data = null;
};
```
### 避免内存泄漏
```javascript
// ❌ 可能导致内存泄漏
const worker = new Worker('worker.js');
worker.onmessage = function(e) {
// 闭包引用大对象
const largeData = e.data;
setTimeout(() => {
console.log(largeData);
}, 10000);
};
// ✅ 及时释放引用
const worker = new Worker('worker.js');
worker.onmessage = function(e) {
const result = process(e.data);
console.log(result);
e.data = null; // 释放引用
};
```
## 5. 错误处理和监控
### 错误处理
```javascript
const worker = new Worker('worker.js');
worker.onerror = function(event) {
console.error('Worker error:', event.message);
console.error('Line:', event.lineno);
console.error('File:', event.filename);
// 根据错误类型决定是否重启 Worker
if (isRecoverable(event.error)) {
restartWorker();
}
};
worker.onmessageerror = function(event) {
console.error('Message error:', event.data);
};
```
### 性能监控
```javascript
class WorkerMonitor {
constructor(worker) {
this.worker = worker;
this.messageCount = 0;
this.totalTime = 0;
this.startTime = null;
this.setupMonitoring();
}
setupMonitoring() {
this.worker.onmessage = (e) => {
if (this.startTime) {
const duration = performance.now() - this.startTime;
this.totalTime += duration;
this.messageCount++;
console.log(`Message ${this.messageCount}: ${duration.toFixed(2)}ms`);
console.log(`Average: ${(this.totalTime / this.messageCount).toFixed(2)}ms`);
}
};
}
sendMessage(data) {
this.startTime = performance.now();
this.worker.postMessage(data);
}
getStats() {
return {
messageCount: this.messageCount,
totalTime: this.totalTime,
averageTime: this.messageCount > 0 ? this.totalTime / this.messageCount : 0
};
}
}
// 使用监控
const monitor = new WorkerMonitor(worker);
monitor.sendMessage(data);
```
## 6. 调试技巧
### 使用 console.log
```javascript
// worker.js
self.onmessage = function(e) {
console.log('[Worker] Received:', e.data);
const result = process(e.data);
console.log('[Worker] Result:', result);
self.postMessage(result);
};
```
### 使用 Chrome DevTools
1. 打开 Chrome DevTools
2. 切换到 "Sources" 面板
3. 在左侧找到 Worker 脚本
4. 设置断点进行调试
### 使用 postMessage 调试
```javascript
// 主线程
worker.postMessage({ type: 'debug', data: { key: 'value' } });
// Worker
self.onmessage = function(e) {
if (e.data.type === 'debug') {
console.log('[Worker Debug]', e.data.data);
}
};
```
## 最佳实践总结
1. **复用 Worker**:避免频繁创建和销毁
2. **使用 Worker 池**:管理多个 Worker 实例
3. **Transferable Objects**:大数据使用转移而非拷贝
4. **批量处理**:减少消息传递次数
5. **分块处理**:大数据分块处理,定期报告进度
6. **及时释放资源**:使用完毕后终止 Worker
7. **错误处理**:添加完善的错误处理机制
8. **性能监控**:监控 Worker 性能指标
9. **WebAssembly**:计算密集型任务使用 WASM
10. **SharedArrayBuffer**:需要共享内存时使用(注意安全限制)
服务端 · 2月21日 15:11
Web Worker 有哪些类型,它们之间有什么区别?Web Worker 有多种类型,每种类型适用于不同的使用场景。
## 1. Dedicated Worker(专用 Worker)
### 特点
- 专属于创建它的页面或脚本
- 一对一关系:一个 Dedicated Worker 只能被一个脚本使用
- 最常用的 Worker 类型
### 创建方式
```javascript
// 从外部文件创建
const worker = new Worker('worker.js');
// 从 Blob URL 创建(内联 Worker)
const workerCode = `
self.onmessage = function(e) {
self.postMessage(e.data * 2);
};
`;
const blob = new Blob([workerCode], { type: 'application/javascript' });
const worker = new Worker(URL.createObjectURL(blob));
```
### 使用场景
- 单页面的后台计算任务
- 图像处理
- 数据加密/解密
- 复杂的数学计算
## 2. Shared Worker(共享 Worker)
### 特点
- 可以被多个页面或脚本共享
- 多对多关系:多个脚本可以连接到同一个 Shared Worker
- 适用于跨标签页的通信和状态共享
### 创建方式
```javascript
// 创建 Shared Worker
const sharedWorker = new SharedWorker('shared-worker.js');
// 连接到 Worker
sharedWorker.port.start();
// 发送消息
sharedWorker.port.postMessage('Hello');
// 接收消息
sharedWorker.port.onmessage = function(event) {
console.log('Received:', event.data);
};
```
### Shared Worker 内部实现
```javascript
// shared-worker.js
const connections = [];
self.onconnect = function(event) {
const port = event.ports[0];
connections.push(port);
port.onmessage = function(e) {
// 广播消息给所有连接
connections.forEach(conn => {
conn.postMessage(e.data);
});
};
port.start();
};
```
### 使用场景
- 多标签页状态同步
- 跨窗口通信
- 共享数据缓存
- 协同编辑应用
## 3. Service Worker(服务 Worker)
### 特点
- 作为网络代理,拦截和处理网络请求
- 独立于页面生命周期运行
- 用于实现离线功能和推送通知
- 必须在 HTTPS 环境下运行(localhost 除外)
### 注册方式
```javascript
// 注册 Service Worker
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js')
.then(function(registration) {
console.log('Service Worker registered:', registration);
})
.catch(function(error) {
console.log('Registration failed:', error);
});
}
```
### Service Worker 实现
```javascript
// service-worker.js
const CACHE_NAME = 'my-cache-v1';
const urlsToCache = [
'/',
'/styles/main.css',
'/script/main.js'
];
// 安装事件
self.addEventListener('install', function(event) {
event.waitUntil(
caches.open(CACHE_NAME)
.then(function(cache) {
return cache.addAll(urlsToCache);
})
);
});
// 拦截请求
self.addEventListener('fetch', function(event) {
event.respondWith(
caches.match(event.request)
.then(function(response) {
if (response) {
return response;
}
return fetch(event.request);
})
);
});
```
### 使用场景
- PWA(渐进式 Web 应用)
- 离线缓存
- 后台同步
- 推送通知
- 网络请求优化
## 4. Worker 类型的对比
| 特性 | Dedicated Worker | Shared Worker | Service Worker |
|------|------------------|---------------|----------------|
| 作用域 | 单页面 | 多页面共享 | 全局代理 |
| 生命周期 | 随页面销毁 | 所有连接断开后销毁 | 独立运行 |
| DOM 访问 | ❌ | ❌ | ❌ |
| 网络请求 | ❌ | ❌ | ✅ |
| HTTPS 要求 | ❌ | ❌ | ✅ |
| 通信方式 | postMessage | port.postMessage | postMessage + fetch |
## 5. 其他 Worker 类型
### AudioWorklet
- 用于音频处理
- 在音频渲染线程中运行
- 提供更精确的音频处理能力
```javascript
const audioContext = new AudioContext();
audioContext.audioWorklet.addModule('audio-processor.js')
.then(() => {
const workletNode = new AudioWorkletNode(audioContext, 'audio-processor');
});
```
### OffscreenCanvas
- 在 Worker 中进行 Canvas 渲染
- 提升复杂图形渲染性能
```javascript
const worker = new Worker('canvas-worker.js');
const offscreen = canvas.transferControlToOffscreen();
worker.postMessage({ canvas: offscreen }, [offscreen]);
```
## 选择建议
- **Dedicated Worker**:大多数后台计算任务
- **Shared Worker**:需要跨标签页共享状态
- **Service Worker**:需要离线功能或网络请求控制
- **AudioWorklet**:音频处理相关
- **OffscreenCanvas**:复杂 Canvas 渲染
服务端 · 2月21日 15:10
如何调试 Web Worker?Web Worker 的调试比主线程调试更具挑战性,但有多种工具和技巧可以帮助开发者高效调试 Worker 代码。
## Chrome DevTools 调试
### 1. 查看 Worker 线程
1. 打开 Chrome DevTools(F12)
2. 切换到 "Sources" 面板
3. 在左侧找到 "Threads" 或 "Workers" 部分
4. 选择对应的 Worker 线程进行调试
### 2. 在 Worker 中设置断点
```javascript
// worker.js
self.onmessage = function(e) {
const data = e.data;
// 在这里设置断点
const result = processData(data);
self.postMessage(result);
};
function processData(data) {
// 可以在这里设置断点查看变量
let sum = 0;
for (let i = 0; i < data.length; i++) {
sum += data[i];
}
return sum;
}
```
### 3. 使用 console.log
```javascript
// worker.js
self.onmessage = function(e) {
console.log('[Worker] Received message:', e.data);
const result = heavyComputation(e.data);
console.log('[Worker] Computation result:', result);
self.postMessage(result);
};
```
## Firefox 开发者工具
### 1. 调试 Worker
1. 打开 Firefox 开发者工具(F12)
2. 切换到 "Debugger" 面板
3. 在左侧找到 "Workers" 部分
4. 展开并选择要调试的 Worker
### 2. Worker 控制台
```javascript
// worker.js
// 在 Firefox 中,Worker 的 console.log 会显示在主控制台
console.log('Worker message');
// 也可以使用 Worker 特定的调试信息
self.postMessage({ type: 'debug', message: 'Debug info' });
```
## 调试技巧
### 1. 消息追踪
```javascript
// 主线程
const worker = new Worker('worker.js');
// 添加消息发送追踪
const originalPostMessage = worker.postMessage.bind(worker);
worker.postMessage = function(data) {
console.log('[Main → Worker]', data);
return originalPostMessage(data);
};
// 添加消息接收追踪
worker.addEventListener('message', function(e) {
console.log('[Worker → Main]', e.data);
});
```
### 2. 错误捕获
```javascript
// 主线程
worker.onerror = function(event) {
console.error('Worker Error:');
console.error('Message:', event.message);
console.error('Filename:', event.filename);
console.error('Line:', event.lineno);
console.error('Column:', event.colno);
console.error('Error Object:', event.error);
};
// Worker 内部
self.onerror = function(event) {
console.error('Worker Internal Error:', event.message);
// 可以选择不阻止默认行为
// event.preventDefault();
};
// 使用 try-catch
self.onmessage = function(e) {
try {
const result = riskyOperation(e.data);
self.postMessage(result);
} catch (error) {
console.error('Error in message handler:', error);
self.postMessage({ error: error.message, stack: error.stack });
}
};
```
### 3. 性能分析
```javascript
// worker.js
self.onmessage = function(e) {
const startTime = performance.now();
const result = heavyComputation(e.data);
const endTime = performance.now();
const duration = endTime - startTime;
console.log(`[Worker] Computation took ${duration.toFixed(2)}ms`);
self.postMessage({ result, duration });
};
```
### 4. 状态监控
```javascript
// worker.js
let state = {
messageCount: 0,
totalProcessingTime: 0,
lastMessageTime: null
};
self.onmessage = function(e) {
const startTime = performance.now();
state.messageCount++;
state.lastMessageTime = new Date().toISOString();
const result = processMessage(e.data);
const endTime = performance.now();
state.totalProcessingTime += (endTime - startTime);
self.postMessage({ result, state });
};
// 定期报告状态
setInterval(() => {
console.log('[Worker] State:', state);
}, 5000);
```
## 高级调试技术
### 1. 使用 Source Map
```javascript
// 在 Worker 脚本中添加 source map
// worker.js
// # sourceMappingURL=worker.js.map
// 或者在创建 Worker 时指定
const worker = new Worker('worker.js');
worker.addEventListener('message', function(e) {
console.log(e.data);
});
```
### 2. 条件断点
在 Chrome DevTools 中设置条件断点:
```javascript
// 在 processData 函数中设置条件断点
// 条件:data.length > 1000
function processData(data) {
let sum = 0;
for (let i = 0; i < data.length; i++) {
sum += data[i];
}
return sum;
}
```
### 3. 日志级别
```javascript
// worker.js
const LogLevel = {
DEBUG: 0,
INFO: 1,
WARN: 2,
ERROR: 3
};
let currentLogLevel = LogLevel.INFO;
function log(level, message, data) {
if (level >= currentLogLevel) {
const prefix = {
[LogLevel.DEBUG]: '[DEBUG]',
[LogLevel.INFO]: '[INFO]',
[LogLevel.WARN]: '[WARN]',
[LogLevel.ERROR]: '[ERROR]'
}[level];
console.log(`${prefix} ${message}`, data || '');
}
}
self.onmessage = function(e) {
log(LogLevel.DEBUG, 'Received message', e.data);
try {
const result = processData(e.data);
log(LogLevel.INFO, 'Processing complete', { result });
self.postMessage(result);
} catch (error) {
log(LogLevel.ERROR, 'Processing failed', { error: error.message });
}
};
```
### 4. 消息序列化检查
```javascript
// 检查消息是否可以正确序列化
function checkSerializable(data) {
try {
const cloned = structuredClone(data);
console.log('[Worker] Data is serializable');
return true;
} catch (error) {
console.error('[Worker] Data is not serializable:', error);
return false;
}
}
self.onmessage = function(e) {
if (!checkSerializable(e.data)) {
self.postMessage({ error: 'Data is not serializable' });
return;
}
// 处理消息
};
```
## 调试工具和库
### 1. Worker DevTools Extension
使用 Chrome 扩展 "Worker DevTools" 可以:
- 查看 Worker 的 console 输出
- 检查 Worker 的网络请求
- 监控 Worker 的性能
### 2. 使用调试代理
```javascript
// debug-worker.js
const originalWorker = self.Worker;
self.Worker = function(scriptURL) {
console.log('[Debug] Creating Worker:', scriptURL);
const worker = new originalWorker(scriptURL);
const originalPostMessage = worker.postMessage.bind(worker);
worker.postMessage = function(data) {
console.log('[Debug] Main → Worker:', data);
return originalPostMessage(data);
};
worker.addEventListener('message', function(e) {
console.log('[Debug] Worker → Main:', e.data);
});
return worker;
};
```
## 常见问题排查
### 1. Worker 无法启动
```javascript
// 检查 Worker 文件路径
const worker = new Worker('/workers/worker.js'); // 确保路径正确
// 检查浏览器支持
if (typeof Worker === 'undefined') {
console.error('Web Workers are not supported in this browser');
}
// 检查同源策略
// 确保 Worker 脚本与主页面同源
```
### 2. 消息未到达
```javascript
// 检查是否启动了端口(SharedWorker)
const sharedWorker = new SharedWorker('worker.js');
sharedWorker.port.start(); // 必须调用 start()
// 检查消息格式
// 确保消息可以被结构化克隆
```
### 3. 内存泄漏
```javascript
// 使用 Chrome DevTools 的 Memory 面板
// 1. 拍摄堆快照
// 2. 执行操作
// 3. 再次拍摄堆快照
// 4. 比较两个快照,查找内存增长
// Worker 中释放引用
self.onmessage = function(e) {
const result = process(e.data);
self.postMessage(result);
e.data = null; // 释放引用
};
```
## 最佳实践
1. **使用有意义的日志**:添加清晰的日志信息
2. **错误处理**:在 Worker 和主线程都添加错误处理
3. **性能监控**:监控 Worker 的执行时间和资源使用
4. **消息追踪**:追踪消息的发送和接收
5. **使用调试工具**:充分利用浏览器开发者工具
6. **条件断点**:使用条件断点减少调试时间
7. **日志级别**:使用不同的日志级别过滤信息
8. **定期检查**:定期检查 Worker 的状态和性能
服务端 · 2月21日 15:10