提起 “worker” 的话,你能想起什么来呢 --
是“咱们工人有力量”?

还是“伐伐伐...伐木工”?

当然,稍有经验的开发者可能已经从标题猜出,今天真正要说的是 -- JavaScript 中的 worker 们:
在 HTML5 规范中提出了工作线程(Web Worker)的概念,允许开发人员编写能够脱离主线程、长时间运行而不被用户所中断的后台程序,去执行事务或者逻辑,并同时保证页面对用户的及时响应。
Web Worker 又分为 Dedicated Worker 和 SharedWorker。
随后 ServiceWorker 也加入进来,用于更好的控制缓存和处理请求,让离线应用成为可能。
I. 进程和线程
先来复习一下基础知识:
- 进程(process)和线程(thread)是操作系统(OS) 里面的两个基本概念
- 对于 OS 来说,一个任务就是一个进程;比如 Chrome 浏览器每打开一个窗口就新建一个进程
- 一个进程可以由多个线程组成,它们分别执行不同的任务;比如 Word 可以借助不同线程同时进行打字、拼写检查、打印等
- 区别在于:每个进程都需要 OS 为其分配独立的内存地址空间,而同一进程中的所有线程共享同一块地址空间
- 多线程可以并发(时间上快速交替)执行,或在多核 CPU 上并行执行

传统页面中(HTML5 之前)的 JavaScript 的运行都是以单线程的方式工作的,虽然有多种方式实现了对多线程的模拟(例如:JavaScript 中的 setinterval 方法,setTimeout 方法等),但是在本质上程序的运行仍然是由 JavaScript 引擎以单线程调度的方式进行的。

为了避免多线程 UI 操作的冲突(如线程1要求浏览器删除DOM节点,线程2却希望修改这个节点的某些样式风格),JS 将处理用户交互、定时执行、操作DOM树/CSS样式树等,都放在了 JS 引擎的一个线程中执行。
从 2008 年 W3C 制定出第一个 HTML5 草案开始,HTML5 承载了越来越多崭新的特性和功能。它不但强化了 Web 系统或网页的表现性能,而且还增加了对本地数据库等 Web 应用功能的支持。
随之而来的,还有上面提到的几种 worker,首先解决的就是多线程的问题。
II. Master-Worker 模式
那么,来看看解决线程问题的东西为什么叫 worker,这来源于一种设计模式:
Master-Worker模式是常用的并行设计模式。其核心思想是:系统有两个进程协同工作:Master进程和Worker进程。Master进程负责接收和分配任务,Worker进程负责处理子任务。当各个Worker进程将子任务处理完后,将结果返回给Master进程,由Master进行归纳和汇总,从而得到系统结果


实例:Node.js 中的 Master-Worker 模式
Node 的内置模块 cluster,可以通过一个主进程管理若干子进程的方式来实现集群的功能
const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;
if (cluster.isMaster) { //主进程
console.log(`Master ${process.pid} is running`);
//分配子任务
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
} else { //子进程
//应用逻辑根本不需要知道自己是在集群还是单边
//每个HTTP server都能监听到同一个端口
http.createServer((req, res) => {
res.writeHead(200);
res.end('hello world\n');
}).listen(8000);
console.log(`Worker ${process.pid} started`);
}
运行 node server.js 后,输出:
Master 3596 is running
Worker 4324 started
Worker 4520 started
Worker 6056 started
Worker 5644 started
III. Web Worker
在 HTML5 中,
Web Worker的出现使得在 Web 页面中进行多线程编程成为可能
HTML5 中的多线程是这样一种机制:它允许在 Web 程序中并发执行多个 JavaScript 脚本,每个脚本执行流都称为一个线程,彼此间上下文互相独立,并且由浏览器中的 JavaScript 引擎负责管理
HTML5 规范列出了 Web Worker 的三大主要特征:
- 能够长时间运行(响应)
- 理想的启动性能
- 理想的内存消耗
HTML5 中的 Web Worker 可以分为两种不同线程类型,一个是专用线程 Dedicated Worker,一个是共享线程 Shared Worker。
(3.1) 专用线程 Dedicated Worker
专用线程是指标准 worker,一个专用 worker 仅仅能被生成它的脚本所使用
也就是说,所谓的专用线程(dedicated worker)并没有一个显示的DedicatedWorker构造函数,其实指的就是普通的Worker构造函数。
🌰 在解释概念前,先来看一个呗儿简单的小栗子:
//myWorker.js
self.onmessage = function(event) {
var info = event.data;
self.postMessage(info + " from worker!");
};
//主页面
<input type="text" name="wkInput1" />
<button id="btn1">test!</button>
<script>
if (window.Worker) {
const myWorker = new Worker("myWorker.js");
myWorker.onmessage = function (event) {
alert(event.data);
};
const btn = document.querySelector('#btn1');
btn.addEventListener('click', e=>{
const ipt = document.querySelector('[name=wkInput1]');
const info = "hello " + ipt.value;
myWorker.postMessage(info);
});
}
</script>
很显然,运行的效果无非是点击按钮后弹出包含文本框内容的字符串。
例子很简单,但携带的关键信息还算丰富,那么结合规范中的一些定义来看一看上面的代码:
首先是专用 worker 在运行的过程中,会隐式的使用一个MessagePort对象,其接口定义如下:
interface MessagePort {
void postMessage(message, optional transfer = []);
attribute onmessage;
void start();
void close();
attribute onmessageerror;
};
我们把最重要的两个成员放在了前面,一个是postMessage()方法,另一个是onmessage属性。
postMessage()方法用来发送数据:第一个参数除了可以发送字符串,还可以发送 JS 对象(有的浏览器需要JSON.stringify());可选的第二个参数可用来发送 ArrayBuffer 对象数据(一种二进制数组,配合XHR、File API、Canvas等读取字节流数据用)onmessage属性应被指定一个事件处理函数,用于接收传递过来的消息;也可以选择使用 addEventListener 方法,其实现方式和作用和 onmessage 相同
然后来看看简化后的 Worker 的定义:
interface AbstractWorker {
attribute onerror;
};
interface Worker {
Constructor(scriptURL, optional workerOptions);
void terminate();
void postMessage(message, optional transfer = []);
attribute onmessage;
attribute onmessageerror;
};
Worker implements AbstractWorker;
- 首先它实现了
AbstractWorker接口,也就是说有一个onerror回调用来管理错误;
myWorker.onerror = function(event){
console.log(event.message);
console.log(event.filename);
console.log(event.lineno);
}
- 其次,明显也实现了上面提到过的
MessagePort接口,可以postMessage/onmessage; - 实例化一个新的 worker 只需要调用 Worker() 的构造方法,指定一个脚本的 URI
- 当创建完 worker 以后,可以调用
terminate()方法去终止该线程
通过workerOptions 中的选项可以支持 es6 模块化等,这里不展开论述
至此,已经可以理解“主页面”中的各种定义和调用行为了;而"myWorker.js"中的self又是怎样的呢,继续来看看相关定义:
interface WorkerGlobalScope {
readonly attribute self; //WorkerGlobalScope
readonly attribute location;
readonly attribute navigator;
void importScripts(urls);
attribute onerror;
attribute onlanguagechange;
attribute onoffline;
attribute ononline;
attribute onrejectionhandled;
attribute onunhandledrejection;
};
interface DedicatedWorkerGlobalScope {
readonly attribute name;
void postMessage(
message,
optional transfer = []
);
void close();
attribute onmessage;
attribute onmessageerror;
};
专用 worker 实现了以上两个接口,可知:
worker中的全局对象就是其本身- 可以使用
WorkerGlobalScope的self只读属性来获得这个对象本身的引用 - 并且可以调用相关的
MessagePort接口方法。
看起来很简单,两边都可以 postMessage/onmessage,就可以愉快的通信了。
除了上述这些,其他的一些要点包括:
- 为了安全,在 worker 中不能访问 DOM
- 作为参数传递给 worker 构造器的 URI 必须遵循同源策略
- worker 不能访问 window 对象,也不能调用 alert()
- 可以在只读的 navigator 对象中访问 appName、appVersion、platform、onLine 和 userAgent 等
- 可以在只读的 location 对象中获取 hostname 和 port 等
- 在 worker 中也支持 XMLHttpRequest 和 fetch 等
- 支持 importScripts() 方法(在同一个域上异步引入脚本文件),该函数接受0个或者多个URI作为参数
- 支持 JavaScript 对象,比如 Object、Array、Date、Math 和 String
- 支持 setTimeout() 和 setInterval() 方法
- 在主线程中使用时,onmessage 和 postMessage() 必须挂在worker对象上,而在worker中使用时不用这样做。原因是,在worker内部,worker是有效的全局作用域
专用 worker 相对理想的兼容情况
在现代浏览器和移动端上,可以说专用 worker 已经被支持的不错了:

(3.2) 共享线程 Shared Worker
共享线程指的是一个可以被多个页面通过多个连接所使用的 worker
🌰 还是先看一个栗子:
//wk.js
var arr = [];
self.onconnect = function(e) {
var port = e.ports[0];
port.postMessage('hello from worker!');
port.onmessage = function(evt) {
var val = evt.data;
if (!~arr.indexOf(val)) {
arr.push(val);
}
port.postMessage(arr.toString());
}
}
<!DOCTYPE html>
<html><body>
page 1
<script>
if (window.SharedWorker) {
var wk = new SharedWorker('wk.js');
wk.port.onmessage = function(e) {
console.log(e.data);
}
wk.port.postMessage(1);
}
//输出
//hello from worker!
//1
</script>
</body></html>
<!DOCTYPE html>
<html><body>
page 2
<script>
if (window.SharedWorker) {
var wk = new SharedWorker('wk.js');
wk.port.onmessage = function(e) {
console.log(e.data);
}
wk.port.postMessage(2);
}
//输出
//hello from worker!
//1,2
</script>
</body></html>
运行效果也不难理解,引用共享 worker 的两个同域的页面,共享了其中的 arr 数组。也就是说,专用 worker 一旦被某个页面引用,该页面就拥有了一个独立的子线程上下文;与之不同的是,某个共享 worker 脚本文件如果被若干页面(要求是同源的)引用,则这些页面会共享该 worker 的上下文,拥有共同影响的变量等。
interface SharedWorker {
Constructor(
scriptURL,
optional (DOMString or WorkerOptions) options
);
readonly attribute port;
};
SharedWorker implements AbstractWorker;
另一个非常大的区别在于,前面也提到过,与一个专用 worker 通信,对MessagePort的实现是隐式进行的(直接在 worker 上进行postMessage/onmessage);而共享 worker 必须通过端口(MessagePort类型的worker.port)对象进行。
此外的几个注意点:
var wk = new SharedWorker('wk.js', 'foo');
//or
var wk = new SharedWorker('wk.js', {name: 'foo'});
interface SharedWorkerGlobalScope {
readonly attribute name;
void close();
attribute onconnect;
};
- 实例化时添加的第二个参数,用于指定这个共享 worker 的名称,必须要同名才能共享;这个 name 在 worker 中可以藉由
self.name获得
self.onconnect = function(e) {
var port = e.ports[0];
port.postMessage('hello from worker!');
//...
}
- 在共享 worker 的 onconnect 回调中直接发送了一个 postMessage,用于提示连接成功,这个动作在页面刷新后并不会重新执行,而是重新打开页面才会执行。
var wk = new SharedWorker('wk.js');
wk.port.onmessage = function(e) {
console.log(e.data);
}
//-->
wk.port.addEventListener('message', function(e) {
console.log(e.data);
});
wk.port.start();
- 如果用
addEventListener代替onmessge,则需要额外调用start()方法才能建立连接
共享大法虽好,兼容仍需谨慎
移动端尚不支持、IE11/Edge也没戏;测试时 Mac 端的 chrome/firefox 也是状况频频无法成功,最后在 chrome@win10 以及 opera@mac 才可以

IV. Service Worker

Service Worker 基于 Web Worker 的事件驱动,提供了用来管理安装、版本、升级的一整套系统。
专用 worker 或共享 worker 专注于解决 “耗时的 JS 执行影响 UI 响应” 的问题, -- 一是后台运行 JS,不影响主线程;二是使用postMessage()/onmessage消息机制实现了并行。
而 service worker 则是为解决 “因为依赖并容易丢失网络连接,从而造成 Web App 的用户体验不如 Native App” 的问题而提供的一系列技术集合;它比 web worker 独立得更彻底,可以在页面没有打开的时候就运行。
并且相比于已经被废弃的 Application Cache 缓存技术:
<html manifest="appcache.manifest">
...
</html>
CACHE MANIFEST
# appcache.manifest text file, version: 0.517
NETWORK:
#CACHE:
assets/loading.gif
assets/wei_shop_bk1.jpg
assets/wei_shop_bk2.jpg
assets/wei_ios/icons.png
assets/wei_ios/icon_addr.png
assets/wei_ios/icon_tel.png
NETWORK:
scripts/wei_webapp.js
styles/meishi_wei.css
service worker 拥有更精细、更完整的控制;作为一个页面与服务器之间代理中间层,service worker 可以捕获它所负责的页面的请求,并返回相应资源,这使离线 web 应用成为了可能。
🌰 一如既往的先看一个直观的小栗子:
<!--http://localhost:8000/service.html-->
<h1>hello service!</h1>
<img src="deer.png" />
<script>
if (navigator.serviceWorker) {
window.onload = function() {
navigator.serviceWorker.register(
'myService.js',
{scope: '/'}
).then(registration=>{
console.log('SW register OK with scope: ', registration.scope);
registration.onmessage = function(e) {
console.log(e.data)
}
}).catch(err=>{
console.log('SW register failed: ', err);
});
}
// SW register OK with scope: http://localhost:8000/
}
</script>
//myService.js
var CACHE_NAME = 'my-site-cache-v1';
var 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) {
const url = new URL(event.request.url);
if (url.pathname == '/deer.png') {
event.respondWith(
fetch('/horse.jpg').catch(ex=>console.log(ex))
);
} else {
event.respondWith(
caches.match(event.request)
.then(function(response) {
if (response) {
return response;
}
return fetch(event.request);
}
)
);
}
});
结合这个简单的示例,来梳理一下其中反映出的信息:
(4.1) 基础组成元素
Promise
和其他两种 worker 不同的是,service worker 中的各项技术广泛地利用了 Promise
Promises 是一种非常适用于异步操作的机制,一个操作依赖于另一个操作的成功执行。这也成为了 service worker 的通用工作机制
Response 对象
Response 的构造函数允许创建一个自定义的响应对象:
new Response('<p>Hello from service worker!</p>', {
headers: { 'Content-Type': 'text/html' }
})
但更常见的是:通过其他的 API 操作返回了一个 Response 对象,例如一个 service worker 的 event.respondWith ,或者一个简单的 fetch()
在 service worker 中使用 Response 对象时,通常还要通过 response.clone() 来取得一个克隆使用;这样做的原因是,一个 response 是一个流,只用被消费一次,而我们想让浏览器、缓存等多次操作这个响应,就需要 clone 出不同的对象来;对于 Request 请求对象的使用也是类似的道理
Fetch
在 service worker 中无法使用传统的 XMLHttpRequest,只能使用 fetch;而后者的优势正在于,可以使用 Request 和 Response 对象
每次网络请求,都会触发对应的 service worker 中的 fetch 事件

在我们的例子中,页面上有一个指向 deer.png 的图片元素,最后却由 fetch 事件回调拦截并返回了 /horse.jpg,实现了指鹿为马的自定义资源指向
self.addEventListener('fetch', function(event) {
const url = new URL(event.request.url);
if (url.pathname == '/deer.png') {
event.respondWith(
fetch('/horse.jpg').catch(ex=>console.log(ex))
);
}
});


Catch
在 service worker 规范中包含了原生的缓存能力,用以替代已被废弃的 Application Cache 标准。
Cache API 提供了一个网络请求的持久层,并可以使用 match 操作查询这些请求。
在 service worker 中最主要用到 Cache 的地方,还是在上面提到的 fetch 事件回调中。
通过使用本地缓存中的资源,不但能省去对网络的昂贵访问,更有了在 离线、掉线、网络不佳 等情况下维持应用可用的能力。
相关的定义如下:
interface Cache {
match(request, optional cacheQueryOptions);
matchAll(optional request, optional cacheQueryOptions);
add(request);
addAll(requests);
put(request, response);
delete(request, optional cacheQueryOptions);
keys(optional request, optional cacheQueryOptions);
};
同时 service worker 也可以用 self.caches 来取得缓存:
interface WindowOrWorkerGlobalScope {
readonly attribute caches; //CacheStorage
};
interface CacheStorage {
match(request, optional options); //Promise
has(cacheName); //Promise
open(cacheName); //Promise
delete(cacheName); //Promise
keys(); //Promise
};
反映在例子中就是(版本的部分会在稍后提到):
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);
}
)
);
});
(4.2) 生命周期
完整的生命周期


将 server worker 的生命周期设计成这样,其目的在于:
- 实现离线优先
- 允许新服务工作线程自行做好运行准备,无需中断当前的服务工作线程
- 确保整个过程中作用域页面由同一个服务工作线程(或者没有服务工作线程)控制
- 确保每次只运行网站的一个版本
对应的事件

重要的比如:
install事件:使用register()注册时会触发activate事件:register()注册时也会触发activate事件
具体到各个事件的回调中,event 参数对应的类型如下:
| 事件名称 | 接口 |
|---|---|
| install | ExtendableEvent |
| activate | ExtendableEvent |
| fetch | FetchEvent |
| message | ExtendableMessageEvent |
| messageerror | MessageEvent |
其中有代表性的两个事件的定义如下:
interface ExtendableEvent {
void waitUntil(promiseFunc);
};
interface FetchEvent {
readonly attribute request;
readonly attribute clientId;
readonly attribute reservedClientId;
readonly attribute targetClientId;
void respondWith(promiseFunc);
};
所以,才可以在例子中调用 event.waitUntil() 和 event.respondWith():
self.addEventListener('install', function(event) {
event.waitUntil( //用一个 promise 检查安装是否成功
//...
);
});
self.addEventListener('fetch', function(event) {
event.respondWith( // 返回符合期望的 Response 对象
//...
);
注册
不同于其他两种 worker 的是,service worker 不再用 new 来实例化,而是直接通过 navigator.serviceWorker 取得
navigator.serviceWorker 实际上实现了 ServiceWorkerContainer 接口:
interface ServiceWorkerContainer {
readonly attribute controller;
readonly attribute ready; //promise
register(scriptURL, optional registrationOptions);
getRegistration(optional clientURL = "");
getRegistrations();
void startMessages();
attribute oncontrollerchange;
attribute onmessage; // event.source is a worker
attribute onmessageerror;
};
比如我们在例子中的主页面所做的:
navigator.serviceWorker.register(
'myService.js',
{scope: '/'}
).then().catch()
scope 参数是选填的,可以被用来指定想让 service worker 控制的内容的子目录;service worker 能控制的最大权限层级就是其所在的目录
运行 register() 方法成功的话,会在 navigator.serviceWorker 的 Promise 的 then 回调中得到一个 ServiceWorkerRegistration 类型的对象;
正如例子中所示,主页面中就可以用这个实例化后的 'registration' 对象调用 onmessage 了
interface ServiceWorkerRegistration {
readonly attribute installing;
readonly attribute waiting;
readonly attribute active;
readonly attribute scope;
readonly attribute updateViaCache;
update(); //in promise
unregister(); //in promise
attribute onupdatefound;
};
同时如果 register() 成功,service worker 就在 ServiceWorkerGlobalScope 环境中运行;
也就是说,myService.js 中引用的 self 就是这个类型了,可以调用 self.skipWaiting() 等方法;
这是一个特殊类型的 worker 上下文运行环境,与主运行线程相独立,同时也没有访问 DOM 等能力
interface ServiceWorkerGlobalScope {
readonly attribute clients;
readonly attribute registration;
skipWaiting();
attribute oninstall;
attribute onactivate;
attribute onfetch;
attribute onmessage; // event.source is a client
attribute onmessageerror;
};
和 shared worker 类似,需要小心 service worker 脚本里的全局变量: 每个页面不会有自己独有的worker
安装
在 service worker 注册之后,install 事件会被触发
在 install 回调中,一般执行以下任务:
- 打开制定版本的缓存
- 缓存文件
- 确认所有需要的资源是否被缓存
- 如有指定的任何缓存文件无法下载,则安装步骤将失败
更新
- 更新 service worker 所在的 JavaScript 文件。用户打开页面时,浏览器会尝试在后台重新下载该 JS 文件;如果该文件与其当前所用文件存在字节差异,则将其视为“新版本的 service worker”。
- 新服务工作线程将会启动,且将会触发 install 事件
- 如果 service worker 已经被安装,但是刷新页面时有一个新版本的可用 -- 那么新版本虽会在后台安装,但还不会激活,且进入 waiting 状态
- 当不再有任何已加载的页面在使用旧版的 service worker 的时候,新版本才会激活,并触发其 activate 事件
出现在 activate 回调中的一个常见任务是缓存管理。在这个步骤进行缓存管理,而不是在之前的安装阶段进行,原因在于:如果在 install 步骤中清除了任何旧缓存,则继续控制所有当前页面的任何旧 service worker 将突然无法从缓存中提供文件
self.addEventListener('activate', function(event) {
var cacheWhitelist = ['pages-cache-v1', 'blog-posts-cache-v1'];
event.waitUntil(
caches.keys().then(function(cacheNames) {
return Promise.all(
cacheNames.map(function(cacheName) {
if (cacheWhitelist.indexOf(cacheName) === -1) {
return caches.delete(cacheName);
}
})
);
})
);
});
(4.3) 其他
需要 HTTPS
-
出于安全考虑,目前只能在 HTTPS 环境下才能使用 service worker;不符合则会抛出错误
DOMException: Only secure origins are allowed (see: https://goo.gl/Y0ZkNV). -
在测试时,是可以用
http://localhost进行的
后台同步

后台同步(Background Sync)是基于 service worker 构建的另一个功能。允许用户一次性或按间隔时间请求后台数据同步。
- 即使用户没有为您的网站打开标签,也会如此,仅唤醒 service worker
- 从页面请求执行此操作的权限,用户将收到提示
- 适用于非紧急更新,如社交时间表或新闻文章
navigator.serviceWorker.register('sw.js');
//...
navigator.serviceWorker.ready.then(registration=>{
registration.sync.register('update-leaderboard').then(function() {
// registration succeeded
}, function() {
// registration failed
});
});
//sw.js
self.addEventListener('sync', function(event) {
if (event.id == 'update-leaderboard') {
event.waitUntil(
caches.open('mygame-dynamic').then(function(cache) {
return cache.add('/leaderboard.json');
})
);
}
});
推送

Push API 是基于 service worker 构建的另一个功能。该 API 允许唤醒 service worker 以响应来自操作系统消息传递服务的消息。
- 即使用户没有为您的网站打开标签,也会如此,仅唤醒 service worker
- 从页面请求执行此操作的权限,用户将收到提示
- 适合于与通知相关的内容,如聊天消息、突发新闻或电子邮件
- 同时可用于频繁更改受益于立即同步的内容,如待办事项更新或日历更改
- 需要 google 的 FCM 通道服务,目前国内无法使用
chrome 离线小恐龙的游戏
正是基于 service worker,chrome 在网络不可用时会显示小恐龙冒险的离线游戏,按下空格键,就可以开始了~
(4.4) Service Worker 的浏览器兼容性
由于一些相关的 google 服务无法用,iOS 上对其的支持也有限并在试验阶段,所以尚不具备大规模应用的条件;
但作为渐进式网络应用技术 PWA 中的最重要的组成部分,国内很多厂商已经在尝试推进相关的支持,未来值得期待:

V. 总结
Master-Worker是常用的并行设计模式,用worker表示线程相关的概念就来源于此web worker的出现使得在 Web 页面中进行多线程编程成为可能- 共享线程指的是一个可以被多个页面通过多个连接所使用的 worker,页面会共享 worker 的上下文,拥有共同影响的变量等
- service worker 作为一个页面与服务器之间的 proxy,可以捕获它所负责的页面的请求,并返回相应资源,这使离线 web 应用成为了可能
- 兼容性方面,专用 worker 相对理想一些,共享 worker 不太理想,service worker 值得期待
VI. 参考资料:
- https://superuser.com/questions/257406/can-a-multi-core-processor-run-multiple-processes-at-the-same-time
- http://www.cnblogs.com/whitewolf/p/javascript-single-thread-and-browser-event-loop.html
- http://woshixiguapi.blog.163.com/blog/static/19249969201010113479457/
- http://www.cnblogs.com/Leo_wl/p/5319735.html
- http://www.alloyteam.com/2015/08/nodejs-cluster-tutorial/
- https://php.golaravel.com/function.pcntl-fork.html
- https://developer.mozilla.org/zh-CN/docs/Web/API/Web_Workers_API/Using_web_workers
- https://developer.mozilla.org/zh-CN/docs/Web/API/Service_Worker_API/Using_Service_Workers
- https://www.smashingmagazine.com/2016/02/making-a-service-worker/
- https://developer.mozilla.org/zh-CN/docs/Web/API/Service_Worker_API
- http://blog.88mph.io/2017/07/28/understanding-service-workers/
- https://serviceworke.rs/
- https://aarontgrogg.com/blog/2015/07/20/the-difference-between-service-workers-web-workers-and-websockets/
- https://www.fxsitecompat.com/en-CA/docs/2015/application-cache-api-has-been-deprecated/
- https://24ways.org/2016/http2-server-push-and-service-workers/
- https://ponyfoo.com/articles/backgroundsync
- https://www.ibm.com/developerworks/cn/web/1112_sunch_webworker/
- https://www.ibm.com/developerworks/cn/web/wa-webworkers/index.html
- https://www.quora.com/Whats-the-difference-between-service-workers-and-web-workers-in-JavaScript
- http://jixianqianduan.com/frontend-javascript/2014/06/05/webworker-serviceworker.html
- https://zhuanlan.zhihu.com/p/27264234
- https://developers.google.com/web/fundamentals/instant-and-offline/offline-cookbook/
- http://imweb.io/topic/56592b8a823633e31839fc01
- https://w3c.github.io/ServiceWorker/#service-worker-concept
- http://www.howtobuildsoftware.com/index.php/how-do/97C/service-worker-what-is-the-api-for-unregistering-service-workers-in-chrome-44
- https://developers.google.com/web/fundamentals/primers/service-workers/
- https://developers.google.com/web/fundamentals/instant-and-offline/offline-cookbook/
- https://developers.google.com/web/fundamentals/primers/service-workers/lifecycle#_2
- https://juejin.cn/post/6844903502158757901
- https://nolanlawson.github.io/cascadia-2016/#/37
- https://love2dev.com/blog/what-is-a-service-worker/
