
一、定义
JavaScript Workers 是一种允许在主线程之外运行 JavaScript 代码的机制,实现了真正的多线程编程。它们解决了 JavaScript 单线程执行模型的限制,允许在后台执行耗时任务,而不阻塞主线程的 UI 渲染和交互。
主线程和Worker的关系:JavaScript是单线程的,但为了处理耗时操作,引入了Web Workers,它们可以在后台运行,不阻塞主线程。
二、workers类型
JavaScript 中有三种主要类型的 Workers:
2.1 Web Workers (Dedicated Workers)
专用 Worker,由单个主线程创建且专属于它,随着页面关闭而终止。
最常用的 Worker 类型。
与主线程通过消息传递进行通信。
2.2 Shared Workers
共享 Worker,可以被同源的多个浏览器上下文(如不同标签页、iframe)共享。
适用于需要在多个上下文间共享数据的场景。
通信需要通过端口(port)进行。
2.3 Service Workers
特殊类型的 Worker,作为网络代理运行。
主要用于离线支持、后台同步、推送通知等。
生命周期独立于网页。
必须通过 HTTPS 运行。
三、workers核心特性
3.1 通信机制
使用 postMessage() 方法发送消息。
通过 onmessage 事件接收消息。
消息会被序列化/反序列化,可以传递基本类型和可克隆对象。
3.2 限制
Workers 不能直接访问 DOM。
不能使用某些主线程的 API(如 window 、document)。
可以使用大部分 JavaScript API(如 fetch、setTimeout、IndexedDB)。
3.3 生命周期
Web Workers:由主线程创建,主线程或 Worker 自身可以终止。
Shared Workers:当没有脚本使用时自动终止。
Service Workers:有独立的生命周期(安装、激活、等待、激活、终止)。
四、Web Workers
Web Worker 的核心在于 线程隔离 和 异步通信。它与主线程并行运行,但运行在完全独立的环境中。下面说说Web Worker能做什么和不能做什么?
能做什么✅ | 不能做什么🚫 |
|---|---|
执行计算密集型任务,如图像解码 | 直接操作 DOM、访问 document 对象 |
使用 XMLHttpRequest 或 fetch 发起网络请求 | 使用 window、parent 对象 |
通过 importScripts() 加载其他脚本 | 访问大部分全局变量或函数 |
基础示例
1. 主线程代码(main.js)
主线程负责创建Worker、发送(postMessage)任务和接收(onmessage)结果。
// 1. 创建Worker,指定后台脚本路径
const myWorker = new Worker('worker.js');
// 2. 发送消息,启动Worker任务
myWorker.postMessage({ numbers: [1, 2, 3, 4, 5] });
// 3. 监听来自Worker的消息
myWorker.onmessage = function(event) {
console.log('计算结果:', event.data); // 输出:15
// 收到结果后,可终止Worker以释放资源
// myWorker.terminate();
};
// 4. 错误处理
myWorker.onerror = function(error) {
console.error('Worker发生错误:', error.message);
};2. Worker线程代码(worker.js)
Worker脚本监听主线程指令,执行任务并返回结果。
// 监听主线程发来的消息
self.onmessage = function(event) {
const data = event.data;
// 执行计算密集型任务(例如求和)
const sum = data.numbers.reduce((a, b) => a + b, 0);
// 将结果发送回主线程
self.postMessage(sum);
// Worker也可自行关闭:self.close()
};关键点:主线程与Worker之间的所有数据都是通过复制(结构化克隆算法)来传递的,而非共享。对于大数据(ArrayBuffer),可以使用 可转移对象 来转移所有权,避免复制开销。
结构化克隆算法:
它是一种复制数据的方法,可以处理复杂的数据类型,比如对象、数组、日期等,但不是所有类型都支持,比如函数、DOM节点等。
复制和共享:共享是多个线程访问同一块内存,而复制是各自有自己的副本。
可转移对象:
复制会有性能开销,使用可转移对象(Transferable Objects),当转移所有权后,原来的线程就不能再访问该对象了,新线程获得所有权,这样避免了复制的开销。
可转移对象示例:
// 主线程
const worker = new Worker('worker.js');
const buffer = new ArrayBuffer(1000000); // 1MB的二进制数据
// 第二个参数表示转移所有权
worker.postMessage(buffer, [buffer]);
console.log(buffer.byteLength); // 输出0,因为所有权已经转移。结构化克隆 Vs 可转移对象:
传递方式 | 特点 | 适用场景 |
|---|---|---|
结构化克隆 | 复制数据副本,双方独立 | 小数据、需要保留原数据 |
可转移对象 | 转移数据所有权,发送方失去访问权 | 大数据、追求性能化 |
浏览器兼容性
Web Worker得到了几乎所有现代浏览器的广泛支持。为保障兼容,应在使用前进行特性检测:
if (typeof Worker !== 'undefined') {
// 浏览器支持 Web Worker
const myWorker = new Worker('worker.js');
} else {
// 不支持,需提供降级方案(如改为在主线程异步执行)
console.log('你的浏览器不支持 Web Worker。');
}五、Shared Worker
Shared Worker(共享Worker) 是一种可以被多个浏览器上下文(如不同的标签页、iframe、甚至其他Worker)共享和连接的特殊Web Worker。它是实现同源页面间后台脚本与数据共享的核心机制。
可以将Shared Worker想象成一个运行在后台的独立中心。任何同源页面都可以连接至此中心,并通过端口与它进行独立的双向通信。
5.1 核心特性
5.1.1 多页面共享同一个Worker实例
普通Worker是单页面独享的,而Shared Worker可以被同一域名下的多个页面共享。
就像一个公共服务窗口,可以同时为多个用户提供服务。
5.1.2 基于端口(MessagePort)的通信机制
每个连接到Shared Worker的页面都会获得一个独立的通信端口(MessagePort)。
Shared Worker通过这些端口与不同页面进行通信。
通过 postMessage() 方法在页面与Shared Worker之间传递数据。
支持结构化克隆算法复制数据,也支持可转移对象。
5.1.3 共享数据和状态
所有连接到Shared Worker的页面可以共享Worker内部的数据。
适合实现跨页面的数据同步和状态管理。
5.1.4. 独立的生命周期
当第一个页面连接时创建并启动。
只要有页面连接,Shared Worker就会保持运行。
当所有页面断开连接后,Shared Worker会被终止。
5.1.5 独立的执行环境
运行在主线程之外的独立线程中。
拥有自己的全局作用域 SharedWorkerGlobalScope。
无法直接访问DOM、window对象或页面的其他资源。
5.2 工作原理简单示例
5.2.1 Shared Worker 代码 (shared-worker.js)
// Shared Worker内部代码 (shared-worker.js)
// 保存所有连接的端口
const connections = [];
// 监听连接
self.onconnect = (event) => {
const port = event.ports[0];
// 将新端口加入连接列表
connections.push(port);
// 开始通信
port.start();
// 接收消息
port.onmessage = (event) => {
const message = event.data;
// 向所有连接的端口广播消息
connections.forEach(clientPort => {
clientPort.postMessage(`Broadcast: ${message.message}`);
});
};
};5.2.2 主线程代码
// 创建 Shared Worker
const worker = new SharedWorker('shared-worker.js');
// 连接到端口
const port = worker.port;
// 开始通信
port.start();
// 发送消息
port.postMessage({ message: 'Hello from Tab 1' });
// 接收消息
port.onmessage = (event) => {
console.log('Received:', event.data);
};5.3 适用场景
跨页面数据同步(如购物车状态、用户登录状态)。
多页面协作应用。
共享计算资源(避免重复计算)。
实时数据推送(如聊天应用)。
5.4 与普通Worker的主要区别
特性 | Web Worker | Shared Worker |
|---|---|---|
共享范围 | 单页面 | 多页面共享 |
通信方式 | 直接postMessage | 通过 MessagePort |
生命周期 | 页面关闭即终止 | 所有连接断开才终止 |
适用场景 | 单页面后台计算 | 多页面协作与共享 |
Shared Worker的核心价值在于提供了一种高效、安全的跨页面通信和数据共享机制,同时保持了Worker的线程隔离特性,避免了直接操作DOM的复杂性。
六、Service Workers
Service Workers 是一种运行在浏览器后台的脚本,与网页分离,拥有独立的生命周期。它们提供了强大的网络代理能力,允许开发者控制页面如何处理网络请求,并支持离线体验、推送通知等高级功能。
运行在浏览器后台的脚本,与网页分离:
想象你打开了一个购物网站(网页),这个网站有个「后台管家」(Service Workers)在浏览器的后台默默工作。
即使你切换到其他标签页或暂时关闭购物网站,这个「管家」可能还在后台运行,不会跟着网页一起关闭。
它和网页是分开的,就像你家的「管家」不会因为你出门就停止工作一样。
6.1 核心特性
6.1.1 网络代理与缓存控制
Service Workers 可以拦截和处理所有网络请求,实现自定义缓存策略:
管家会帮你「管理快递」(网络请求):
当你浏览网站时,所有需要加载的图片、文字、数据都像「快递」,需要从服务器(快递公司)发送到你的浏览器。
管家会拦截这些「快递请求」,然后决定:
如果之前已经存了相同的「快递」(缓存),就直接拿给你,不用再等服务器发送,速度更快;
如果是新「快递」,就去服务器取,同时还会把新「快递」存一份,下次备用。
// Service Worker 中拦截并缓存请求
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request)
.then((response) => {
// 如果缓存中有响应,则返回缓存内容
if (response) {
return response;
}
// 否则发起网络请求
return fetch(event.request)
.then((networkResponse) => {
// 将新的响应添加到缓存
if (networkResponse && networkResponse.status === 200) {
const responseToCache = networkResponse.clone();
caches.open('v1')
.then((cache) => {
cache.put(event.request, responseToCache);
});
}
return networkResponse;
});
})
);
});6.1.2 离线功能
通过缓存关键资源,Service Workers 可以让应用在离线时继续运行:
管家的「特殊技能」让网站更像手机APP:
离线体验:即使你断网了,管家因为之前缓存了网站的核心内容(首页、商品列表等),你依然能浏览这些内容,就像没断网一样。
推送通知:即使你没打开购物网站,管家也能帮你接收网站的「促销消息」(推送通知),比如「你关注的商品降价了」,直接显示在浏览器的通知栏里。
// 安装时缓存核心资源
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open('v1')
.then((cache) => {
return cache.addAll([
'/',
'/index.html',
'/styles/main.css',
'/script/main.js',
'/images/logo.png'
]);
})
);
});6.2 推送通知
支持后台接收推送消息并显示通知:
// 处理推送消息
self.addEventListener('push', (event) => {
const data = event.data.json();
const options = {
body: data.body,
icon: '/images/icon.png',
badge: '/images/badge.png'
};
event.waitUntil(
self.registration.showNotification(data.title, options)
);
});
// 处理通知点击
self.addEventListener('notificationclick', (event) => {
event.notification.close();
event.waitUntil(
clients.openWindow('https://example.com')
);
});6.3 生命周期
Service Workers 有严格的生命周期:
1. 注册 (Register):在主线程中注册 Service Worker。
// 主线程中注册 Service Worker
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js')
.then((registration) => {
console.log('SW registered: ', registration);
})
.catch((registrationError) => {
console.log('SW registration failed: ', registrationError);
});
});
}2. 安装 (Install):首次注册时触发,用于缓存核心资源。
上班:你第一次访问网站时,浏览器会自动「雇佣」这个管家(安装 Service Workers)。
3. 激活 (Activate):安装完成后触发,用于清理旧缓存。
工作:安装完成后,管家正式开始工作,接管网站的网络请求、缓存等任务。
4. 等待 (Waiting):如果有旧版本的 Service Worker 正在运行,新版本会进入等待状态。
5. 闲置 (Idle):Service Worker 处于休眠状态,等待事件触发。
6. 终止 (Terminate):浏览器可以随时终止闲置的 Service Worker 以节省资源。
休息(闲置/终止):如果一段时间没活干,浏览器会让管家暂时休息(终止)以节省资源。
7. 唤醒 (Fetch/Push):当有 fetch、push 等事件触发时,Service Worker 会被唤醒。
唤醒(触发):当你再次访问网站、有网络请求或需要推送通知时,管家会被重新唤醒。
七、三种 Workers 技术对比
特性 | Web Workers | Shared Workers | Service Workers |
|---|---|---|---|
基本概念 | 运行在后台的独立 JavaScript 线程,与主线程并行执行 | 可以被多个浏览上下文(如多个标签页、iframe)共享的 Worker | 运行在浏览器后台的脚本,与网页分离,提供网络代理和离线能力 |
生命周期 | 随创建它的页面关闭而终止 | 当所有关联的浏览上下文关闭后终止 | 独立于页面生命周期,即使页面关闭仍可运行 |
通信方式 | 通过 postMessage() 和 onmessage 与主线程通信 | 通过 MessagePort 与多个页面通信,需要显式连接 | 通过 postMessage() 与页面通信,可监听 fetch、push 等事件 |
作用域 | 只能被创建它的页面访问 | 可以被同源的多个页面共享访问 | 可以控制同源的所有页面 |
应用场景 | 处理 CPU 密集型任务,避免阻塞主线程 | 多个页面共享计算结果或状态,如实时协作应用 | 离线功能、推送通知、后台同步、性能优化 |
网络访问 | 可以使用 XMLHttpRequest / fetch 发起网络请求 | 可以使用 XMLHttpRequest / fetch 发起网络请求 | 可以拦截和处理所有网络请求,实现自定义缓存策略 |
缓存能力 | 无内置缓存机制 | 无内置缓存机制 | 拥有 Cache API ,可缓存任意网络资源 |
浏览器支持 | 所有现代浏览器,IE10+ | 现代浏览器,IE 不支持 | 现代浏览器,需要 HTTPS(localhost 除外) |
安全限制 | 无法访问 DOM,只能使用部分 Web API | 无法访问 DOM,只能使用部分 Web API | 无法访问 DOM,只能在 HTTPS 下运行(localhost 除外) |
主要特性 |
|
|
|
创建方式 | | | |

通过合理使用 JavaScript Workers,可以显著提升 web 应用的性能和用户体验,特别是在处理复杂或耗时任务时。