咱们worker有力量-在浏览器中玩转多线程和离线应用

2,959 阅读18分钟

提起 “worker” 的话,你能想起什么来呢 --

是“咱们工人有力量”?

image

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

image

当然,稍有经验的开发者可能已经从标题猜出,今天真正要说的是 -- JavaScript 中的 worker 们

在 HTML5 规范中提出了工作线程(Web Worker)的概念,允许开发人员编写能够脱离主线程、长时间运行而不被用户所中断的后台程序,去执行事务或者逻辑,并同时保证页面对用户的及时响应。

Web Worker 又分为 Dedicated WorkerSharedWorker

随后 ServiceWorker 也加入进来,用于更好的控制缓存和处理请求,让离线应用成为可能。

I. 进程和线程

先来复习一下基础知识:

  • 进程(process)和线程(thread)是操作系统(OS) 里面的两个基本概念
  • 对于 OS 来说,一个任务就是一个进程;比如 Chrome 浏览器每打开一个窗口就新建一个进程
  • 一个进程可以由多个线程组成,它们分别执行不同的任务;比如 Word 可以借助不同线程同时进行打字、拼写检查、打印等
  • 区别在于:每个进程都需要 OS 为其分配独立的内存地址空间,而同一进程中的所有线程共享同一块地址空间
  • 多线程可以并发(时间上快速交替)执行,或在多核 CPU 上并行执行

image

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

image

为了避免多线程 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进行归纳和汇总,从而得到系统结果

image

image

实例: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中的全局对象就是其本身
  • 可以使用 WorkerGlobalScopeself 只读属性来获得这个对象本身的引用
  • 并且可以调用相关的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

image

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;而后者的优势正在于,可以使用 RequestResponse 对象

每次网络请求,都会触发对应的 service worker 中的 fetch 事件

image

在我们的例子中,页面上有一个指向 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) 生命周期

完整的生命周期

image
image

将 server worker 的生命周期设计成这样,其目的在于:

  • 实现离线优先
  • 允许新服务工作线程自行做好运行准备,无需中断当前的服务工作线程
  • 确保整个过程中作用域页面由同一个服务工作线程(或者没有服务工作线程)控制
  • 确保每次只运行网站的一个版本

对应的事件

image

重要的比如:

  • 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

  1. 出于安全考虑,目前只能在 HTTPS 环境下才能使用 service worker;不符合则会抛出错误

    DOMException: Only secure origins are allowed (see: https://goo.gl/Y0ZkNV).

  2. 在测试时,是可以用 http://localhost 进行的

后台同步

image

后台同步(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');
      })
    );
  }
});

推送

image

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/


(end)



长按二维码或搜索 fewelife 关注我们哦