也不彻底是笔记,也作了一些本身的补充
javascript是单线程的,可是javascript能够把工做嫁接给独立的线程。同时不影响单线程模型(不能操做DOM)。javascript
每打开一个网页就至关于一个沙盒,每个页面都有本身独立的内容。工做者线程至关于一个彻底独立的二级子环境。在子环境中不能与依赖单线程模型API交互(DOM操做),可是能够与父环境并行执行代码。css
在工做者线程中,没有window对象,全局对象是WorkerGlobalScope的子类的实例html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <script> // 在本地调试,须要使用绝对路径 const worker = new Worker('./worker.js') console.log(worker) </script> </body> </html>
工做者线程的脚本文件,只能和父级同源。(可是在工做者线程中,可使用importScripts加载其余源的脚本)java
建立的Worker对象在工做线程终止前,是不会被垃圾回收机制回收的
工做者线程中全局做用域是DedicatedWorkerGlobalScope对象的实例,能够经过self关键字访问全局对象git
生命周期分为初始化,活动,终止。但父上下文是没法区分工做者线程的状态。调用Worker后,虽然worker对象可用,可是worker不必定初始化完毕。可能存在延迟。若是不调用close或者terminate,工做者线程会一直存在,垃圾回收机制也不会回收worker对象。可是调用close和terminate是有一些区别的。若是工做者线程关联的网页被关闭,工做者线程也会被终止。github
// 专用工做者线程 self.postMessage('a') self.close() self.postMessage('b') // 父上下文 const worker = new Worker('./worker.js') worker.onmessage = ({ data }) => { console.log('data:', data); } // consloe // data: a // data: b
// 工做者线程 self.onmessage = ({data}) => console.log(data); // 父上下文 const worker = new Worker(location.href + '/worker.js') // 定时器等待线程初始化完成 setTimeout(() => { worker.postMessage('a') worker.terminate() worker.postMessage('b') }, 1000); // consloe // a
专用工做者线程能够经过Blob对象的URL在行内建立,而不须要远程的js文件。web
const workerStr = ` self.onmessage = ({data}) => { console.log('data:', data); } `; const workerBlob = new Blob([workerStr]); const workerBlobUrl = URL.createObjectURL(workerBlob); const worker = new Worker(workerBlobUrl); // data: abc worker.postMessage('abc');
父上下文的函数,也能够传递给专用工做者线程,而且在专用工做者线程中执行。可是父上下文的函数中不能使用闭包的变量,以及全局对象。算法
const fn = () => '父上下文的函数'; // 将fn转为字符串的形式,而后自执行 const workerStr = ` self.postMessage( (${fn.toString()})() ) ` const workerBlob = new Blob([workerStr]); const workerBlobUrl = URL.createObjectURL(workerBlob); const worker = new Worker(workerBlobUrl); worker.onmessage = ({ data }) => { // 父上下文的函数 console.log(data) }
const a = 'Hi' // error, Uncaught ReferenceError: a is not defined const fn = () => `${a}, 父上下文的函数`; const workerStr = ` self.postMessage( (${fn.toString()})() ) ` const workerBlob = new Blob([workerStr]); const workerBlobUrl = URL.createObjectURL(workerBlob); const worker = new Worker(workerBlobUrl); worker.onmessage = ({ data }) => { // 父上下文的函数 console.log(data) }
工做者线程中,可使用importScripts加载执行脚本。importScripts加载的js会按照顺序执行。全部导入的脚本会共享做用域,importScripts不会同源的限制。api
我通过测试,在父上下文中使用 onerror 监听错误,是能够捕获到importScripts加载的非同源脚本的错误,而且有具体的错误信息。数组
// 父上下文 const worker = new Worker('http://127.0.0.1:8080/worker.js') window.onerror = (error) => { // Uncaught ReferenceError: a is not defined console.log(error) } // 工做者线程 importScripts('http://127.0.0.1:8081/worker.js') // 工做者线程中importScripts加载的脚本 const fn = () => { console.log(a) } setTimeout(() => fn(), 3000);
工做线程还能够继续建立工做者线程。可是多个工做者线程会带来额外的开销。而且顶级工做者线程,和子工做者线程,必须和父上下文在同一个源中。
try……catch, 没法捕获到线程中的错误,可是在父上下文中,可使用onerror事件捕获到
以前已经在demo中给出例子,这里再也不赘述
MessageChannel API有两个端口,若是父上下文须要实现与工做线程的通信, 父上下文须要将端口传到工做者线程中
// 父上下文 const channel = new MessageChannel() const worker = new Worker('http://127.0.0.1:8080/worker.js') // 将端口2发送给工做者线程中 worker.postMessage(null, [channel.port2]); setTimeout(() => { // 经过MessageChannel发送消息 channel.port1.postMessage('我是父上下文') }, 2000)
// 工做线程 let channelPort = null self.onmessage = ({ ports }) => { if (!channelPort) { channelPort = ports[0] self.onmessage = null // 经过channelPort监听消息 channelPort.onmessage = ({ data }) => { console.log('父上下文的数据:', data); } } }
同源脚本可使用BroadcastChannel进行通信,使用BroadcastChannel必须注意的是,若是父上下文在工做线程初始化完成以前,就发送消息,工做线程初始化完成后,是接受不到消息的。消息不会存在消息队列中。
// 父上下文 const channel = new BroadcastChannel('worker') const worker = new Worker('http://127.0.0.1:8080/worker.js') // 等待工做线程初始化完毕 setTimeout(() => { channel.postMessage('消息') }, 2000)
// 工做线程 const channel = new BroadcastChannel('worker') channel.onmessage = ({ data }) => { console.log(data) }
Channel Messaging API 能够用在 "文档主体与iframe","两个iframe之间","使用SharedWorker的两个文档",或者两个"worker"之间进行通许。
使用postMessage发送数据的时候,浏览器后台会对数据(除了Symbol以外的类型)进行拷贝。虽然结构化克隆算法对循环引用的问题作了兼容处理,可是对于复杂对象结构化克隆算法有性能损耗。
将数据的全部权。由父级上下文转让给工做线程。或者由工做线程转让给父级上下文。转移后,数据就会在以前的上下文中被抹去。postMessage的第二个参数,是可选参数,是一个数组,数组的数据须要被转让全部权的数据。
// 父上下文 const worker = new Worker('http://127.0.0.1:8080/worker.js') const buffer = new ArrayBuffer(30) // 30 console.log('发送以前:', buffer.byteLength) // 等待工做线程初始化完毕 setTimeout(() => { worker.postMessage(buffer, [buffer]) // 0 console.log('发送以后:', buffer.byteLength) }, 2000)
// 工做线程 self.onmessage = ({ data }) => { // 30 console.log('工做线程接受以后', data.byteLength); }
以前可使用 worker.postMessage(null, [channel.port2]) 发送channel接口的时候。工做线程的onmessage事件的参数,会接收ports,可是换成其余数据是接收不到的。postMessage应该是对channel的数据作了特殊的处理。
SharedArrayBuffer能够在父上下文和工做线程中共享,SharedArrayBuffer和ArrayBuffer的api相同,不能直接被操做须要视图。
// 父上下文 const worker = new Worker('http://127.0.0.1:8080/worker.js') const sharedBuffer = new SharedArrayBuffer(10) const view = new Int8Array(sharedBuffer); view[0] = 1; // 1 console.log('发送以前:', view[0]); worker.postMessage(sharedBuffer) setTimeout(() => { // 打印出,2 console.log('发送以后:', view[0]) }, 2000)
// 工做者线程 self.onmessage = ({ data }) => { const view = new Int8Array(data); view[0] = '2' }
并行线程共享资源,会有资源征用的隐患。可使用Atomics解决,Atomics与SharedArrayBuffer能够查看第二十章的笔记。
开启新的工做者线程开销很大,可开启保持固定数量的线程。线程在忙碌时不接受新任务,线程空闲后接收新任务。这些长期开启的线程,被称为线程池。
线程池中线程的数量,能够参考电脑cpu的线程数, navigator.hardwareConcurrency, 将cpu的线程数设置线程池的上限。
下面的是数中的封装,我在github中也没有找到太热门的库封装的线程池可用,https://github.com/andywer/th... 已是5年前更新的了。
注意:
共享工做者线程和建立,安全限制和专用工做者线程都是相同的,共享工做者线程,能够看做是专用工做者线程的扩展。
SharedWorker能够被多个同源的上下文(同源的网页标签)访问。SharedWorker的消息接口和专用工做者线程也略有不一样。
SharedWorker,没办法使用行内的worker, 由于经过URL.createObjectURL, 是浏览器内部的URL, 没法在其余标签页使用。
worker每次new都会返回一个新的worker实例,SharedWorker只会在不存在相同标示的状况下返回新的实例。SharedWorker的标示能够是worker文件的路径, 文档源。
// 只会实例化一个共享工做者线程 new SharedWorker('http://127.0.0.1:8080/worker.js') new SharedWorker('http://127.0.0.1:8080/worker.js')
可是若是咱们给相同源的SharedWorker,不一样的标识,浏览器会任务它们是不一样的共享工做者线程
// 实例化二个共享工做者线程 new SharedWorker('http://127.0.0.1:8080/worker.js', { name: 'a' }) new SharedWorker('http://127.0.0.1:8080/worker.js', { name: 'a' })
在不一样页面,只要标示相同,建立的SharedWorker都是同一个连接
SharedWorker对象的属性
共享工做者线程中的全局对象是SharedWorkerGlobalScope的实例,全局实例上的属性和方法
专用工做者线程只和一个页面绑定,而共享工做者线程只要还有一个上下文连接,它就不会被回收。共享工做者对象没法经过terminate关闭,由于共享工做者线程没有terminate方法,浏览器会负责管理共享工做者线程的连接。
发生connect事件时,SharedWorker构造函数会隐式的建立MessageChannel,并把其中一个port转移给共享工做者线程的ports数组中。
? 可是共享线程与父上下文的启动关闭不是对称的。每次打开会创建连接,connect事件中的ports数组中port数量会加1,可是页面被关闭,SharedWorker没法感知。
好比不少页面连接了SharedWorker,如今一部分如今关闭了,SharedWorker并不知道那些页面关闭,因此ports数组中,存在被关闭的页面的port,这些死端口会污染ports数组。
书中给出的方法是,能够在页面销毁以前的beforeunload事件中,通知SharedWorker清除死端口。
// 父页面 const worker = new SharedWorker('http://127.0.0.1:8080/worker.js') worker.port.onmessage = ({ data }) => { // 打印 data: 2 console.log('data:', data); } worker.port.postMessage([1, 1]);
// 共享工做者线程 const connectedPorts = new Set(); self.onconnect = ({ports}) => { if (!connectedPorts.has(ports[0])) { connectedPorts.add(ports[0]) ports[0].onmessage = ({ data }) => { ports[0].postMessage(data[0] + data[1]) } } };
分享线程生成id,标示接口,并发送给页面。在页面beforeunload,将id发送给分享工做者线程中,分享工做者线程清除死端口。
// 父页面1 const worker = new SharedWorker('http://127.0.0.1:8080/worker.js') let portId = null worker.port.onmessage = ({ data }) => { if (typeof data === 'string' && data.indexOf('uid:') > -1) { // 记录接口的id portId = data.split(':')[1]; } else { console.log('接口的数量:', data); } } window.addEventListener('beforeunload', (event) => { worker.port.postMessage(`删除:${portId}`); });
// 父页面2 const worker = new SharedWorker('http://127.0.0.1:8080/worker.js') let portId = null worker.port.onmessage = ({ data }) => { if (typeof data === 'string' && data.indexOf('uid:') > -1) { // 记录接口的id portId = data.split(':')[1]; } else { console.log('接口的数量:', data); } } window.addEventListener('beforeunload', (event) => { worker.port.postMessage(`删除:${portId}`); });
// 分享工做者线程 const uuid = () => { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); return v.toString(16); }); } // 记录接口的map const connectedPortsMap = new Map(); self.onconnect = ({ports}) => { if (!connectedPortsMap.has(ports[0])) { const uid = uuid(); connectedPortsMap.set(uid, ports[0]) // 向页面发送接口的id,这个id用于删除接口 ports[0].postMessage(`uid:${uid}`); ports[0].onmessage = ({ data }) => { if (typeof data === 'string' && data.indexOf('删除:') > -1) { const portId = data.split(':')[1]; // 删除死接口 connectedPortsMap.delete(portId); } } } }; setInterval(() => { // 发送接口的数量 connectedPortsMap.forEach((value) => { value.postMessage(connectedPortsMap.size) }) }, 3000)
? 感受这个章节翻译的有点差,不少话读的很别扭,不流畅。并且不少章节都没有给出示例代码,我不少章节都手敲了一遍例子代码,放在文章李
是浏览器中的代理服务器线程,能够拦截请求或者缓存响应,页面能够在无网的环境下使用。与共享工做者线程相似,多个页面共享一个服务工做者线程。服务工做者线程中,服务工做者线程可使用Notifications API、Push API、Background Sync API。为了使用Push API服务工做者线程能够在浏览器或者标签页关闭后,继续等待推送的事件。
服务工做者线程,经常使用于网络请求的缓存层和启用推送通知。服务工做者线程,能够把Web应用的体验变为原生应用程序同样。
浏览器用于显示桌面通知的API, 下面是例子
// 检查是否容许发送通知 // 若是已经容许直接发送通知 if (Notification.permission === "granted") { let notification = new Notification('西尔莎罗南', { body: '西尔莎罗南?' }); } else if (Notification.permission !== "denied") { // 若是尚未容许发送通知,咱们请求用户容许 Notification.requestPermission().then(function (permission) { // 若是用户接授权限,咱们就能够发起一条消息 if (permission === "granted") { let notification = new Notification('西尔莎罗南', { body: '西尔莎罗南?' }); } }) }
Push API实现了Web接受服务器推送消息的能力。Push API具体的实施代码,能够看个人这个例子, 实现了一个简单的推送。
过程,客户端生成订阅信息,发送给服务端保存。服务端端能够根据须要,在合适的时候,使用订阅信息向客户端发送推送。
https://github.com/peoplesing...
服务工做者线程,用于按期更新数据的API。
? 原本想实验以一下这个API,可是注册定时任务时,提示“DOMException: Permission denied.”错误,暂时没有解决。
服务工做者线程没有全局的构造函数,经过 navigator.serviceWorker 建立,销毁,服务工做者线程
与共享工做者线程同样,在没有时建立新的连接,若是线程已存在,连接到已存在的线程上。
// 建立服务工做者线程 navigator.serviceWorker.register('http://127.0.0.1:8080/worker.js');
registerf返回一个Promise对象,在同一页面首次调用register后,后续调用register没有任何返回。
navigator.serviceWorker.register('http://127.0.0.1:8080/worker.js').then(() => { console.info('注册成功') }).catch(() => { console.error('注册失败') })
若是服务工做者线程用于管理缓存,服务工做线程应该在页面中提早注册。不然,服务工做者线程应该在load事件中完成注册。
if ('serviceWorker' in navigator) { window.addEventListener('load', () => { navigator.serviceWorker.register('http://127.0.0.1:8080/worker.js').then(() => { console.info('注册成功') }).catch(() => { console.error('注册失败') }) }); }
ServiceWorkerContainer对象是浏览器对服务工做者线程的顶部封装,ServiceWorkerContainer能够在客户端中经过navigator.serviceWorker访问
const btn = document.getElementById('btn') btn.onclick = () => { navigator.serviceWorker.register('./sw2.js') } navigator.serviceWorker.oncontrollerchange = () => { console.log('触发controllerchange事件') // sw2 console.log(navigator.serviceWorker.controller) } navigator.serviceWorker.register('./worker.js')
// sw1 console.log('hehe')
// sw2 self.addEventListener('install', async () => { // 强制进入已激活的状态 self.skipWaiting(); }) self.addEventListener('activate', async () => { // 强制接管客户端 self.clients.claim(); })
if ('serviceWorker' in navigator) { window.addEventListener('load', () => { let sw1; let sw2; navigator.serviceWorker.register('http://127.0.0.1:8080/worker.js').then(sw => { console.log(sw); sw1 = sw; }) navigator.serviceWorker.ready.then((sw) => { console.log(sw); sw2 = sw; }) setTimeout(() => { // true console.log(sw1 === sw2) }, 1000) }); }
ServiceWorkerRegistration,表示成功注册的服务工做者线程。能够经过register返回的Promise中访问到。在同一页面调用register,若是URL相同,返回的都是同一个ServiceWorkerRegistration对象。
navigator.serviceWorker.register('http://127.0.0.1:8080/worker.js').then(sw => { // ServiceWorkerRegistration对象 console.log(sw); })
如何获取ServiceWorker对象?有两种如下的途径
ServiceWorker对象继承Work,可是不包含terminate方法
若是在非HTTPS的协议下,navigator.serviceWorker是undefined。window.isSecureContext能够判断当前上下文是否安全。
服务工做者线程内部,全局对象是ServiceWorkerGlobalScope的实例。ServiceWorkerGlobalScope继承WorkerGlobalScope,所以拥有它的属性和方法。线程内部经过self访问全局的上下文。ServiceWorkerGlobalScope的实例的作了如下的扩展。
专用工做者线程,和共享工做者线程只有一个onmessage事件做为输入,但服务工做者线程能够接受多种事件
// worker.js在根目录线下 navigator.serviceWorker.register('http://127.0.0.1:8080/worker.js') // http://127.0.0.1:8080下的全部请求都会被拦截 fetch('http://127.0.0.1:8080/foo.js'); fetch('http://127.0.0.1:8080/foo/fooScript.js'); fetch('http://127.0.0.1:8080/baz/bazScript.js'); // worker.js在foo目录下 navigator.serviceWorker.register('http://127.0.0.1:8080/foo/worker.js'}) // foo目录下的请求会被拦截 fetch('/foo/fooScript.js') // 其余路径的请求不会被拦截 fetch('/foo.js') fetch('/baz/bazScript.js')
若是想排除某个路径下的请求,可使用末尾带斜杠的路径
// foo路径下的请求,都不会被拦截 navigator.serviceWorker.register( 'http://127.0.0.1:8080/worker.js', { scope: '/foo/' } )
经过 self.caches 访问 CacheStorage 对象。CacheStorage时字符串和Cache对象的映射。CacheStorage在页面或者其余工做者线程中,均可以访问使用。
// 访问缓存,若是没有缓存则会建立 self.caches.open(key)
CacheStorage也拥有相似Map的API,好比has,delete,keys(),可是它们都是返回Promise的
分别返回匹配的第一个Response
(async () => { const request = new Request('https://www.foo.com') const response1 = new Response('fooResponse1') const response2 = new Response('fooResponse2') const v1 = await caches.open('v1') await v1.put(request, response1) const v2 = await caches.open('v2') await v2.put(request, response2) const matchCatch = await caches.match(request) const matchCatchText = await matchCatch.text() // true console.log(matchCatchText === 'fooResponse1') })();
CacheStorage对象是字符串和Cache对象的映射。Cache对象则是Request对象或者URL字符串,和Response对象之间的映射。
Cache也拥有delete(), keys()等方法,这些方法都是返回Promise的
(async () => { const request1 = new Request('https://www.foo.com'); const response1 = new Response('fooResponse'); const cache = await caches.open('v1') await cache.put(request1, response1) const keys = await cache.keys() // [Request] console.log(keys) })()
(async () => { const request1 = new Request('https://www.foo.com?a=1&b=2') const request2 = new Request('https://www.bar.com?a=1&b=2', { method: 'GET' }) const response1 = new Response('fooResponse') const response2 = new Response('barResponse') const v3 = await caches.open('v3') await v3.put(request1, response1) await v3.put(request2, response2) const matchResponse = await v3.match(new Request('https://www.foo.com'), { ignoreMethod: true, // 忽略匹配GET或者POST方法 ignoreSearch: true, // 忽略匹配查询字符串 }); const matchResponseText = await matchResponse.text() // fooResponse console.log(matchResponseText) })();
catch对象的key,value使用的是Request, Response对象的clone方法建立的副本
(async () => { const request = new Request('https://www.foo.com'); const response = new Response('fooResponse'); const cache = await caches.open('v1') await cache.put(request, response) const keys = await cache.keys() // false console.log(keys[0] === request) })();
获取存储空间,以及目前以用的空间
navigator.storage.estimate()
一开始注册服务工做者时,页面将在下一次加载以前才使用它。有两种方法能够提早控制页面
// 页面 navigator.serviceWorker.register('./worker.js').then((registration) => { setTimeout(() => { fetch('/aa') }, 2000) }).catch(() => { console.log('注册失败') });
// sw self.addEventListener('fetch', () => { // sw没有控制客户端,因此没法拦截fetch请求,抛出错误 throw new Error('呵呵') })
第一种解决方法, 使用claim强制得到控制权,可是可能会形成版本资源不一致
self.addEventListener('activate', async () => { self.clients.claim(); }) self.addEventListener('fetch', () => { // 能够抛出错误 throw new Error('呵呵') })
第二种解决方法,刷新页面
navigator.serviceWorker.register('./worker.js').then((registration) => { setTimeout(() => { fetch('/aa') }, 3000) registration.addEventListener('updatefound', () => { const sw = registration.installing; sw.onstatechange = () => { console.log('sw.state', sw.state) if (sw.state === 'activated') { console.log('刷新页面') // 刷新页面后能够抛出错误 window.location.reload(); } } }) }).catch(() => { console.log('注册失败') });
服务工做者线程,最重要的就是保持一致性(不会存在a页面使用v1版本的服务工做者线程,b页面使用v2版本的服务工做者线程)。
数据一致性,
调用 navigator.serviceWorker.register() 会进入已解析的状态,可是该状态没有事件,也没有对应的ServiceWorker.state的值。
在客户端能够经过检查registration.installing是否被设置为了ServiceWorker实例,判断是否在安装中的状态。当服务工做者线程到达安装中的状态时,会触发onupdatefound事件。
navigator.serviceWorker.register('./sw1.js').then((registration) => { registration.onupdatefound = () => { console.log('我已经达到了installing安装中的状态') } console.log(registration.installing) });
在服务工做者线程的内部,能够经过监听install事件,肯定安装中的状态。
在install事件中,能够用来填充缓存,可使用waitUntil的方法,waitUntil方法接受一个Promise,只有Promise返回resolve时,服务工做者线程的状态才会向下一个状态过渡。
self.addEventListener('install', (event) => { event.waitUntil(async () => { const v1 = await caches.open('v1') // 缓存资源完成后,才过渡到下一个状态 v1.addAll([ 'xxxx.js', 'xxx.css' ]) }) })
在客户端能够经过检查registration.waiting是否被设置为了ServiceWorker实例,判断是不是已安装的状态。若是浏览器中没有以前版本的的ServiceWorker,新安装的ServiceWorker会直接跳过这个状态,进入激活中的状态。不然将会等待。
navigator.serviceWorker.register('./worker.js').then((registration) => { console.log(registration.waiting) });
若是有已安装的ServiceWorker,可使用self.skipWaiting,强制工做者线程进入活动的状态
若是浏览器中没有以前版本的ServiceWorker,则新的服务工做者线程会直接进入这个状态。若是有其余服务者线程,能够经过下面的方法,使新的服务者线程进入激活中的状态
const btn = document.getElementById('btn'); navigator.serviceWorker.register('./sw1.js').then((registration) => { // 第一次加载没有活动的(以前版本)服务工做者进程, waiting直接跳过因此为null console.log('waiting:', registration.waiting); // 当前激活的是sw1的服务工做者线程 console.log('active:', registration.active); }); btn.onclick = () => { navigator.serviceWorker.register('./sw2.js').then((registration) => { // 加载新版本的服务工做者线程,触发更新加载 // 由于以前已经有在活动的服务工做者线程了,waiting状态的是sw2的线程 console.log('waiting:', registration.waiting); // 激活状态的是sw1的线程 console.log('active:', registration.active); }) }
在客户端中能够大体经过判断registration.active是否为ServiceWorker的实例判断。(active为ServiceWorker的实例,多是是激活状态或者激活中的状态)
在服务工做者线程中,能够经过添加activate事件处理来判断,该事件处理程序经常使用于删除以前的缓存
const CATCH_KEY = 'v1' self.addEventListener('activate', async (event) => { const keys = await caches.keys(); keys.forEach((key) => { if (key !== CATCH_KEY) { caches.delete(key) } }); })
注意:activate事件发生,并不意味着页面受控。可使用clients.claim()控制不受控的客户端。
在客户端中能够大体经过判断registration.active是否为ServiceWorker的实例判断。(active为ServiceWorker的实例,能够是激活状态或者激活中的状态)
或者能够经过查看registration.controller属性,controller属性返回已激活ServiceWorker的实例。当新的服务工做者线程控制客户端时,会触发navigator.serviceWorker.oncontrollerchange事件
或者navigator.serviceWorker.ready返回的Promise为resolve时,工做者线程也是已激活的状态。
const btn = document.getElementById('btn'); navigator.serviceWorker.register('./sw1.js').then((registration) => { // 已激活的线程sw1 console.log('activated', navigator.serviceWorker.controller) }); btn.onclick = () => { navigator.serviceWorker.register('./sw2.js').then((registration) => { // 在等待的线程sw2 console.log('waiting', registration.waiting) // 已激活的线程sw1 console.log('activated', navigator.serviceWorker.controller) }) }
服务工做者会被浏览器销毁并回收资源
下面操做会触发更新检查:
若是更新检查发现差别,浏览器会使用新脚本初始化新的工做者线程,新的工做者线程将会达到installed的状态。而后会等待。除非使用self.skipWaiting(), 强制进入激活中的状态。或者原有的服务工做者线程客户端数量变为0(标签页都被关闭)在下一次导航事件新工做者线程进入激活中的状态。
刷新页面不会让更新服务工做者线程进入激活状态并取代已有的服务工做者线程。好比,有个打开的页面,其中有一个服务工做者线程正在控制它,而一个更新服务工做者线程正在已安装状态中等待。客户端在页面刷新期间会发生重叠,即旧页面尚未卸载,新页面已加载了。所以,现有的服务工做者线程永远不会让出控制权,毕竟至少还有一个客户端在它的控制之下。为此,取代现有服务工做者线程惟一的方式就是关闭全部受控页面。
使用updateViaCache能够控制,服务工做者线程的缓存
navigator.serviceWorker.register('/serviceWorker.js', { updateViaCache: 'none' });
// 页面 navigator.serviceWorker.onmessage = ({ data }) => { console.log('服务者线程发送的消息:', data); } navigator.serviceWorker.register('./worker.js').then((registration) => { console.log('注册成功') }).catch(() => { console.log('注册失败') });
// sw self.addEventListener('install', async () => { self.skipWaiting(); }); self.addEventListener('activate', async () => { self.clients.claim(); const allClients = await clients.matchAll({ includeUncontrolled: true }); let homeClient = null; for (const client of allClients) { const url = new URL(client.url); if (url.pathname === '/') { homeClient = client; break; } } homeClient.postMessage('Hello') });
self.onfetch = (fetchEvent) => { fetchEvent.respondWith(fetch(fetchEvent.request)); };
fetchEvent.respondWith 接受Promise,返回Respose对象,
self.onfetch = (fetchEvent) => { fetchEvent.respondWith(caches.match(fetchEvent.request)); };
self.onfetch = (fetchEvent) => { fetchEvent.respondWith(fetch(fetchEvent.request).catch(() => { return caches.match(fetchEvent.request) })); };
self.onfetch = (fetchEvent) => { fetchEvent.respondWith( caches.match(fetchEvent.request).then((response) => { return response || fetch(fetchEvent.request).then(async (res) => { // 网络返回成功后,将网络返回的资源,缓存到本地 const catch = await catchs.open('CatchName') await catch.put(fetchEvent.request, res) return res; }) }) ); };
在服务者线程加载时就应该缓存资源,在缓存,和网络都失效时候,返回通用的后备。