Web Worker https://www.zoo.team/article/web-worker
众所周知,JavaScript 是单线程的语言。当我们面临需要大量计算的场景时(比如视频解码等),UI 线程就会被阻塞,甚至浏览器直接卡死。现在前端遇到大量计算的场景越来越多,为了有更好的体验,HTML5 中提出了 Web Worker 的概念。Web Worker 可以使脚本运行在新的线程中,它们独立于主线程,可以进行大量的计算活动,而不会影响主线程的 UI 渲染。当计算结束之后,它们可以把结果发送给主线程,从而形成了高效、良好的用户体验。Web Worker 是一个统称,具体可以细分为普通的 Worker、SharedWorker 和 ServiceWorker 等,接下来我们一一介绍其使用方法和适合的场景。
const worker = new Worker('./worker.js'); // 参数是 url,这个 url 必须与创建者同源
const worker = new Worker('./worker.js');
worker.onmessage = function (messageEvent) {
console.log(messageEvent)
}
const worker = new Worker('./worker.js');
worker.onmessageerror = function (messageEvent) {
console.log(messageEvent)
}
const worker = new Worker('./worker.js');
worker.postMessage({ type: 'start', payload: { count: 666 } }); // 发送信息给worker
const worker = new Worker('./worker.js');
worker.terminate();
// 监听事件,主线程可以通过 postMessage 发送信息过来
self.onmessage = (messageEvent) => {
const { type, payload } = messageEvent.data;
switch (type) {
case 'start':
// 通过 type 去区分不同的业务逻辑,payload 是传过来的数据
const result = 0;
// ....,通过一系列处理之后,把最终的结果发送给主线程
this.postMessage(result);
break;
}
};
这里我们从 messageEvent.data 中获取从主线程传递过来的数据。为了业务的扩展性,这边是以 type 去区分不同的业务,payload 承载数据源,通过处理之后把结果发给主线程。主线程的 onmessage 回调函数中就能收到这个结果了。
跟常用的 JavaScript 一样,Worker 中也是可以引入其他的模块的。但是方式不太一样,是通过 importScripts 来引入。这边我为了演示,新建了一个 constant.js。在 constant.js 定义了一些变量和函数。
示例:
// Worker.js
importScripts('constant.js');
// 下面就可以获取到 constant.js 中的所有变量了
// constant.js
// 可以在 Worker 中使用
const a = 111;
// 不可以在 Worker 中使用,原因未知
const b = function () {
console.log('test');
};
// 可以在 Worker 中使用
function c() {
console.log('test');
}
SharedWorker 是一种特定的 Worker。从它的命名就能知道,它是一种共享数据的 Worker。它可以同时被多个浏览器环境访问。这些浏览器环境可以是多个 window, iframes 或者甚至是多个 Worker,只要这些 Workers 处于同一主域。为跨浏览器 tab 共享数据提供了一种解决方案。
const worker = new SharedWorker("./shareWorker.js"); // 参数是 url,这个 url 必须与创建者同源
const sharedWorker = new SharedWorker('./shareWorker.js');
sharedWorker.port.onmessage = function (messageEvent) {
console.log(messageEvent)
}
const sharedWorker = new SharedWorker('./shareWorker.js');
sharedWorker.port.postMessage({ type: 'increase', payload: { count: 666 } });
const sharedWorker = new SharedWorker('./shareWorker.js');
sharedWorker.port.start()
const sharedWorker = new SharedWorker('./shareWorker.js');
sharedWorker.port.close()
// index.js
const worker = new SharedWorker('./shareWorker.js');
worker.port.start(); // 开启端口
// 发送信息给 shareWorker
worker.port.postMessage({ type: 'increase', payload: { count: 666 } });
// 接受 shareWorker 发过来的数据
worker.port.onmessage = function (val) {
console.log(val.data)
};
// shareWorker.js
let count = 666;
port.onmessage = (messageEvent) => {
const { type, payload } = messageEvent.data;
switch (type) {
case 'increase':
port.postMessage(++count);
break;
case 'decrease':
port.postMessage(--count);
break;
}
};
ServiceWorker 一般作为 Web 应用程序、浏览器和网络之间的代理服务。他们旨在创建有效的离线体验,拦截网络请求,以及根据网络是否可用采取合适的行动,更新驻留在服务器上的资源。他们还将允许访问推送通知和后台同步 API。
// index.js
if ('serviceWorker' in navigator) {
window.addEventListener('load', function () {
navigator.serviceWorker
.register('./serviceWorker.js', { scope: '/page/' })
.then(
function (registration) {
console.log('ServiceWorker registration successful with scope: ',
registration.scope);
},
function (err) {
console.log('ServiceWorker registration failed: ', err);
}
);
});
}
只要创建了 ServiceWorker,不管这个创建 ServiceWorker 的 html 是否打开,这个 ServiceWorker 是一直存在的。它会代理范围是根据 scope 决定的,如果没有这个参数,则其代理范围是创建目录同级别以及子目录下所有页面的网络请求。代理的范围可以通过 registration.scope 查看。
// serviceWorker.js
const CACHE_NAME = 'cache-v1';
// 需要缓存的文件
const urlsToCache = [
'/style/main.css',
'/constant.js',
'/serviceWorker.html',
'/page/index.html',
'/serviceWorker.js',
'/image/131.png',
];
self.oninstall = (event) => {
event.waitUntil(
caches
.open(CACHE_NAME) // 这返回的是 promise
.then(function (cache) {
return cache.addAll(urlsToCache); // 这返回的是 promise
})
);
};
在上述代码中,我们可以看到,在 install 事件的回调中,我们打开了名字为 cache-v1 的缓存,它返回的是一个 promise。在打开缓存之后,我们需要把要缓存的文件 add 进去,基本上所有类型的资源都可以进行缓存,例子中缓存了 css、js、html、png。如果所有缓存数据都成功,就表示 ServiceWorker 安装成功;如果控制台提示 Uncaught (in promise) TypeError: Failed to execute 'Cache' on 'addAll': Request failed,则表示安装失败。
self.onfetch = (event) => {
event.respondWith(
caches
.match(event.request) // 此方法从服务工作线程所创建的任何缓存中查找缓存的结果
.then(function (response) {
// response 为匹配到的缓存资源,如果没有匹配到则返回 undefined,需要 fetch 资源
if (response) {
return response;
}
return fetch(event.request);
})
);
};
在 fetch 事件的回调中,我们去匹配 cache 中的资源。如果匹配到,则使用缓存资源;没有匹配到则用 fetch 请求。正因为 ServiceWorker 可以代理网络请求,所以为了安全起见,规范中规定它只能在 https 和 localhost 下才能开启。
上面代码中,我缓存了 131.png。切换到离线模式,131 图片还是能显示,134.png 就获取不到了。
看到这里,大家可能会有疑惑了。这个图片它存到哪里去了?实际上它会把文件自动存到浏览器的 Cache Storage 中。我们打开浏览器可以看到。
类型 | Worker | SharedWorker | ServiceWorker |
---|---|---|---|
通信方式 | postMessage | port.postMessage | 单向通信,通过addEventListener 监听serviceWorker 的状态 |
使用场景 | 适合大量计算的场景 | 适合跨 tab、iframes之间共享数据 | 缓存资源、网络优化 |
兼容性 | >= IE 10>= Chrome 4 | 不支持 IE、Safari、Android、iOS>= Chrome 4 | 不支持 IE>= Chrome 40 |
本文介绍了 3 种 Worker,他们分别适合不同的场景,总结如上面表格。普通的 Worker 可以在需要大量计算的时候使用,创建新的线程可以降低主线程的计算压力,不会导致 UI 卡顿。SharedWorker 主要是为不同的 window、iframes 之间共享数据提供了另外一个解决方案。ServiceWorker 可以缓存资源,提供离线服务或者是网络优化,加快 Web 应用的开启速度,更多是优化体验方面的。
示例代码:https://github.com/Pulset/Web-Worker
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有