Web Worker
由来
JS 是单线程。当面临需要大量计算的场景时(如视频解码等),UI 线程就会被阻塞,甚至浏览器卡死。HTML5 中提出了 Web Worker 的概念。
Web Worker 可以使脚本运行在新的线程中,它们独立于主线程,可以进行大量的计算活动,而不会影响主线程的 UI 渲染。当计算结束之后,它们可以把结果发送给主线程,从而形成了高效、良好的用户体验。Web Worker 具体可以细分为普通的 Worker、SharedWorker 和 ServiceWorker 等
普通 Worker
创建 worker
1
| const worker = new Worker('./worker.js');
|
worker 的一些方法
onmessage
主线程中可以在 Worker 上添加 onmessage 方法,用于监听 Worker 的信息。
1 2 3 4
| const worker = new Worker('./worker.js'); worker.onmessage = function (messageEvent) { console.log(messageEvent) }
|
onmessageerror
主线程中可以在 Worker 上添加 onmessageerror 方法,用于监听 Worker 的错误信息。
1 2 3 4
| const worker = new Worker('./worker.js'); worker.onmessageerror = function (messageEvent) { console.log(messageEvent) }
|
postMessage()
主线程通过此方法给 Worker 发送消息,发送参数的格式不限(可以是数组、对象、字符串等),可以根据自己的业务选择。
1 2
| const worker = new Worker('./worker.js'); worker.postMessage({ type: 'start', payload: { count: 666 } });
|
terminate()
主线程通过此方法终止 Worker 的运行。
1 2
| const worker = new Worker('./worker.js'); worker.terminate();
|
worker 作用域
Worker 的作用域跟主线程中的 Window 是相互独立的,并且 Worker 中是获取不到 DOM 元素的。所以在 Worker 中你无法使用 Window 变量。取而代之的是可以用 self 来表示全局对象。比较常用的方法是 onmessage、postMessage,主要用来跟主线程进行通信。
1 2 3 4 5 6 7 8 9 10 11 12
| self.onmessage = (messageEvent) => { const { type, payload } = messageEvent.data; switch (type) { case 'start': const result = 0; this.postMessage(result); break; } };
|
worker 引入其它脚本
1 2 3
| importScripts('constant.js');
|
1 2 3 4 5 6 7 8 9 10 11 12 13
|
const a = 111;
const b = function () { console.log('test'); };
function c() { console.log('test'); }
|
调试

控制台源代码中有专门的 worker.js
常用使用场景
视频网站
一般的视频网站 以优酷为例,当我们开始播放优酷视频的时候,就能看到它会调用 Worker,解码的代码应该写在 Worker 里面。
需要大量计算的网站
比如 imgcook 这个网站,它能在前端解析 sketch 文件,这部分解析的逻辑就写在 Worker 里。
一个例子说明 webworker 的效率
不使用 webworker,主线程运行3个斐波那契
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <script> function fb(n){ if(n==1||n==2){ return 1; } return fb(n-1)+fb(n-2) } console.time('三个fb执行时间'); var result=fb(40) var result=fb(40) var result=fb(40) console.timeEnd('三个fb执行时间') </script> </body> </html>
|
时间:9669ms
使用 webworker,创建三个进程,每个进程运行一个函数
1 2 3 4 5 6 7 8 9 10 11 12 13
| var worker1=new Worker('worker1.js'); var worker2=new Worker('worker2.js'); var worker3=new Worker('worker3.js'); worker1.onmessage=e=>{ console.log(e.data); } worker2.onmessage=e=>{ console.log(e.data); } worker3.onmessage=e=>{ console.log(e.data); }
|
1 2 3 4 5 6 7 8 9 10 11 12
| function fb(n){
if(n==1||n==2){ return 1; } return fb(n-1)+fb(n-2) } console.time('fb执行时间1'); var result=fb(40) console.timeEnd('fb执行时间1') self.postMessage('worker1');
|
1 2 3 4 5 6 7 8 9 10 11 12
| function fb(n){
if(n==1||n==2){ return 1; } return fb(n-1)+fb(n-2) } console.time('fb执行时间2'); var result=fb(40) console.timeEnd('fb执行时间2') self.postMessage('worker2');
|
1 2 3 4 5 6 7 8 9 10 11 12
| function fb(n){
if(n==1||n==2){ return 1; } return fb(n-1)+fb(n-2) } console.time('fb执行时间3'); var result=fb(40) console.timeEnd('fb执行时间3') self.postMessage('worker3');
|
时间分别为 2154、2185、2217
SharedWorker
SharedWorker 是一种共享数据的 Worker。它可以同时被多个浏览器环境访问。这些浏览器环境可以是多个 window, iframes 或者甚至是多个 Worker,只要这些 Workers 处于同一主域。为跨浏览器 tab 共享数据提供了一种解决方案。
创建 worker
1
| const worker = new SharedWorker("./shareWorker.js");
|
SharedWorker 的一些方法
方法都在 port 上
port.onmessage
1 2 3 4
| const sharedWorker = new SharedWorker('./shareWorker.js'); sharedWorker.port.onmessage = function (messageEvent) { console.log(messageEvent) }
|
port.postMessage()
参数格式不限
1 2
| const sharedWorker = new SharedWorker('./shareWorker.js'); sharedWorker.port.postMessage({ type: 'increase', payload: { count: 666 } });
|
post.start()
主线程通过此方法开启 SharedWorker 之间的通信
1 2
| const sharedWorker = new SharedWorker('./shareWorker.js'); sharedWorker.port.start()
|
port.close()
主线程通过此方法关闭 SharedWorker
1 2
| const sharedWorker = new SharedWorker('./shareWorker.js'); sharedWorker.port.close()
|
例子
1 2 3 4 5 6 7 8 9 10 11 12
| const worker = new SharedWorker('./shareWorker.js');
worker.port.start();
worker.port.postMessage({ type: 'increase', payload: { count: 666 } });
worker.port.onmessage = function (val) { console.log(val.data) };
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 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; } };
|
调试
在浏览器中查看和调试 SharedWorker 的代码,需要输入 chrome://inspect/

Service Worker
ServiceWorker 一般作为 Web 应用程序、浏览器和网络之间的代理服务。他们旨在创建有效的离线体验,拦截网络请求,以及根据网络是否可用采取合适的行动,更新驻留在服务器上的资源。他们还将允许访问推送通知和后台同步 API。
创建 ServiceWorker
navigator.serviceWorker.register().then()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| 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 的一些方法
oninstall
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| 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) .then(function (cache) { return cache.addAll(urlsToCache); }) ); };
|
在上述代码中,我们可以看到,在 install 事件的回调中,我们打开了名字为 cache-v1 的缓存,它返回的是一个 promise。在打开缓存之后,我们需要把要缓存的文件 add 进去,基本上所有类型的资源都可以进行缓存,例子中缓存了 css、js、html、png。如果所有缓存数据都成功,就表示 ServiceWorker 安装成功;如果控制台提示 Uncaught (in promise) TypeError: Failed to execute ‘Cache’ on ‘addAll’: Request failed,则表示安装失败。
onfetch
1 2 3 4 5 6 7 8 9 10 11 12 13
| self.onfetch = (event) => { event.respondWith( caches .match(event.request) .then(function (response) { if (response) { return response; } return fetch(event.request); }) ); };
|
在 fetch 事件的回调中,我们去匹配 cache 中的资源。如果匹配到,则使用缓存资源;没有匹配到则用 fetch 请求。正因为 ServiceWorker 可以代理网络请求,所以为了安全起见,规范中规定它只能在 https 和 localhost 下才能开启。
调试
在浏览器中查看和调试 ServiceWorker 的代码,需要输入 chrome://inspect/#service-workers
举个例子
两张图片,缓存了图片1,断网后,图片1仍能显示,控制台/网络能看到请求成功,200状态码后面写了(from service worker),实际上文件被存到了 cacheStorage 中,控制台/应用中可以看到。
使用场景
缓存资源文件,加快渲染速度
总结
类型 |
Worker |
SharedWorker |
ServiceWorker |
通信方式 |
postMessage |
port.postMessage |
单向通信,通过 addEventListener 监听 serviceWorker 的状态 |
使用场景 |
需要大量计算的场景 |
跨 tab、iframes 间共享数据 |
缓存资源、网络优化 |
兼容性 |
>= IE10 >= Chrome4 |
不支持 IE、Safari、Android、iOS >= Chrome 4 |
不支持 IE >= Chrome 40 |