Web worker,service worker和worklet,这些都是“JavaScript Workers”,虽然它们在运行方式上有一些相似的地方,并且它们在使用上也有一些重叠的地方。
一般来说,一个worker是一个脚本在浏览器主线程之外的单独的线程上运行。如果你想要在HTML文档中引用一个<script>
标签的典型的JavaScript文件,它会运行在主线程上。如果主线程上有太多的计算,会拖慢网站的速度,造成交互卡顿和响应延迟。
Web worker,service worker和worklet都是让脚步运行在单独的线程上的,下面讲一下三者的区别。
Web workers
Web workers是最常用的worker类型。它不像另外两种,它们除了运行在主线程外的特性外,没有一个特殊的应用场景。所以,web worker可以用于减少主线程上大量的线程活动。
要使用Web worker,需要使用Web Worker API,创建一个单独的JavaScript文件,将该文件添加到一个新的Worker中。
/* main.js */
const myWorker = new Worker('worker.js');
worker.js文件中的任何代码都会开始运行,web worker最有用的是用于减少那些可能需要花费很长时间的工序或者与其他线程上平行运行。一个很好的例子是图片线程web应用:Squoosh,它使用web worker来处理图片处理任务,可以让主线程有精力处理用户与应用的交互而不被打扰。
像所有的worker,web worker没有获取DOM的权限,这意味着任何需要的信息将被在worker和主脚本间传递,传递参数使用window.postMessage()。
/* main.js */线程
// 创建 worker
const myWorker = new Worker('worker.js');
// 向 worker 传递信息
myWorker.postMessage('Hello!');
// 接收从 worker 传递过来的信息
myWorker.onmessage = function(e) {
console.log(e.data);
}
在worker脚本中,我们可以监听主脚本中的消息,并将响应返回。
/* worker.js */
// 接收主文件的信息
self.onmessage = function(e) {
console.log(e.data);
// 向主文件发送信息
self.postMessage(workerResult);
}
Service workers
Service workers 是一种提供详细的作为浏览器和网络或缓存间的代理的服务。
与Web worker类似,service worker 是在主脚本文件中注册,引用到一个专门的service worker文件。
/* main.js */
navigator.serviceWorker.register('/service-worker.js');
与一般的web worker不同,service worker有一些额外的特性来实现代理的目的。只要它们被安装且被激活,service worker就可以拦截主文档中发起的任何网络请求。
/* service-worker.js */
// Install (安装)
self.addEventListener('install', function(event) {
// ...
});
// Activate (激活)
self.addEventListener('activate', function(event) {
// ...
});
// 监听主文档中的网络请求
self.addEventListener('fetch', function(event) {
// ...
});
只要一被拦截,service worker就可以返回一个缓存中的文档作为响应,而不用走网络请求,因此可以让应用离线运行。
/* service-worker.js */
self.addEventListener('fetch', function(event) {
// 返回缓存中的数据
event.respondWith(
caches.match(event.request);
);
});
Worklets
Worklet 是一个非常轻量且高度特别的worker。它可以让开发者在浏览器渲染线程中的多个部分做钩子。
当一个web页面正在被渲染,浏览器经过很多步骤。在我的文章“Understanding the Critical Rendering Path”中有更加详细的介绍,在这里我们需要关注有四步:Style,Layout,Paint和Composite。
我们来看Paint这一步吧,这一步是浏览器应用每个元素的样式。在渲染的这一步做钩子的worklet是 Paint Worklet。
Paint Worklet允许我们创建自定义图片,这个图片可以应用任何CSS,比如background-image属性的值。
为了创建worklet,我们想其他worker一样在主脚本文件中注册它,并引用到单独的worklet文件。
/* main.js */
CSS.paintWorklet.addModule('myWorklet.js');
在worklet文件中,我们可以创建自定义图片,paint函数像Canvas API一样工作。下面例子中,我们实现一个黑色到白色的渐变。
/* myWorklet.js */
registerPaint('myGradient', class {
paint(ctx, size, properties) {
var gradient = ctx.createLinearGradient(0, 0, 0, size.height / 3);
gradient.addColorStop(0, "black");
gradient.addColorStop(0.7, "rgb(210, 210, 210)");
gradient.addColorStop(0.8, "rgb(230, 230, 230)");
gradient.addColorStop(1, "white");
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, size.width, size.height / 3);
}
});
最后,我们可以在我们CSS中应用这个新的worklet,而且我们创建的这个自定义图片将会应用起来其他任何图片背景。
div {
background-image: paint(myGradient);
}
除了Paint Worklet之外,还有其他的worklet可以向渲染线程的其他阶段做钩子。Animation Worklet在Composite阶段做钩子,Layout Worklet在Layout阶段做钩子。
总结
Web worker,Service worker和worklet都是将脚本运行在浏览器主线程之外单独的线程中,它们之间的区别是它们所应用的场景和他们的特性。
Worklet 是浏览器渲染流中的钩子,可以让我们有浏览器渲染线程中底层的权限,比如样式和布局。
Service worker 是浏览器和网络间的代理。通过拦截文档中发出的请求,service worker 可以直接请求缓存中的数据,达到离线运行的目的。
Web worker 是通常目的是让我们减轻主线程中的密集处理工作的脚本。