工作者线程能把主线程的工作转嫁给独立的实体,而不会改变现有的单线程模型
27.1 工作者线程简介
JS环境实际上是运行在托管操作系统中的虚拟环境
对浏览器来说,管理多个环境很简单,因为所有的环境都是并行的
使用工作者线程,浏览器可以在原始页面环境之外再分配一个完全独立的二级子环境
27.1.1 工作者线程和线程
工作者线程是以实际线程实现的
工作者线程可以并行
工作者线程可以共享某些内存
工作者线程不共享全部内存
工作者线程不一定在同一个进程里
创建工作者线程的开销更大
27.1.2 工作者线程的类型
专用工作者线程:可以让脚本单独创建一个JS线程,只能被创建它的页面使用
共享工作者线程:可以被多个不同的上下文使用
服务工作者线程:主要用于拦截,重定向和修改页面发出的请求
27.1.3 WorkerGlobalScope
在网页上,window可以向运行在其中的脚本暴露各种全局对象
在工作者线程的内部,全局对象是WorkerGlobalScope的实例,通过self暴露出来
1.WorkerGlobalScope的属性和方法
navigator: 返回和工作者线程相关的WorkerNavigator
self: 返回WorkerGlobalScope对象
location: 返回工作者线程相关的WorkerLocation
performance: 返回Performance对象
console: 返回工作者线程相关的Console对象
caches: 返回工作者线程关联的CacheStorage对象
indexedDB: 返回IDBFactory对象
isSecureCintext: 返回标识工作者线程上下文是否安全的布尔值
origin: 返回WorkerGlobalScope的源
类似的,self对象上暴露的方法也是window方法的子集,这些方法和window上对应的方法操作一样
atob()
btoa()
clearInterval()
clearTimeout()
createImageBitmap()
fetch()
setInterval()
setTimeout()
2.WorkerGlobalScope的子类
专用工作者线程用 DedicatedWorkerGlobalScope
共享工作者线程用 SharedWorkerGlobalScope
服务工作者线程用 ServiceWorkerGlobalScope
27.2 专用工作者线程
专用工作者线程是最简单的Web工作者线程,网页中的脚本可以创建专用工作者线程来执行在页面线程之外的其他任务
27.2.1 专用工作者线程的基本概念
可以把专用工作者线程称为后台脚本
1.创建专用工作者线程
可以使用绝对路径/相对路径创建
// 绝对路径
const worker1 = new Worker(location.herf + 'emptyWorker.js')
// 相对路径 要保证main.js和emptyWorker.js在同一个路径下
const worker2 = new Worker('emptyWorker.js')
2.工作者线程安全限制
工作者线程的脚本文件只能从和父页面相同的源加载
如果工作者线程加载的脚本带有全局唯一标识符,就会受父文档安全策略的限制
3.使用Worker对象
Worker()构造函数返回的Worker对象是和刚创建的专用工作者线程通信的连接点
Worker对象支持下面的事件:
onerror: 错误时触发
onmessage: 发生MessageEvent类型的消息事件会触发
onmessageerror: 发生MessageEvent类型的错误事件会触发
postMessage(): 通过异步向工作者线程发送消息
terminate(): 立即终止工作者线程
4.DedicateWorkerGlobalScope
在工作者线程内部,全局作用域是DedicateWorkerGlobalScope的实例
工作者线程可以通过self访问该全局作用域
DedicateWorkerGlobalScope 在 WorkerGlobalScope的基础上添加了如下属性和方法:
name: 给Worker构造函数的一个可以选择的字符串标识符
postMessage(): 和worker.postMessage()对应的方法,用于从工作者线程内部向父上下文发送消息
close(): 立即终止工作者线程,没有为工作者线程提供清理的机会
importScripts(): 用于向工作者线程中导入任意数量的脚本
27.2.2 专用工作者线程和隐式MessagePorts
专用工作者线程使用了隐式MessagePorts在上下文通信
所以有 ommessage,onmessageerror,close(),postMessage() 等和Worker实例相同的属性和方法
27.2.3 专用工作者线程的生命周期
Worker()构造函数是专用工作者线程生命的起点
专用工作者线程可以非正式的区分为下列三个状态: 初始态, 活动态, 终止
在整个生命周期中,一个专用工作者线程只会关联一个网页(文档)
除非明确终止,否则关联文档存在,专用工作者线程就会存在
27.2.4 配置Worker选项
Worker()构造函数允许将可选的配置对象作为第二个参数
name: 可以在工作者线程中通过self.name读取到的字符串标识符
type: 加载脚本的运行方式 classic:常规脚本 module:模块
credentials: 当type = module时,指定如何获取和传输凭证数据相关的工作者线程模块脚本
27.2.5 在JS行内创建工作者线程
专用工作者线程可以使用Blob对象URL在行内创建
// 要执行的JS代码字符串
const workerScript = ` self.onmessage = ({data}) => console.log(data) `
// 基于脚本字符串生成Blob对象
const workerScriptBlob = new Blob([workerScript])
// 基于Blob实例创建对象URL
const workerScriptBlobURL = URL.createObjectURL(workerScriptBlob)
// 基于对象URL创建专用工作者线程
const worker = new Worker(workerScriptBlobURL)
27.2.6 在工作者线程中动态执行脚本
工作者线程线程中可以使用importScripts()方法,通过编程方式加载和执行任意脚本
脚本加载通常受到CORS的限制,但是工作者线程内部可以请求来自任何源的脚本
27.2.7 委托子任务到子工作者线程
除了路径解析不同,其他步骤和创建普通工作者线程是一样的
27.2.8 处理工作者线程错误
如果工作者线程脚本抛出了错误,该工作者线程沙盒可以阻止它打断父线程的执行
但是响应的错误事件任然会冒泡到工作者线程的上下文,可以在Worker对象上面设置错误事件侦听器访问到
27.2.9 与专用工作者线程通信
p802
1.使用postMessage()
2.使用MessageChannel
MessageChannel有两个端口,分别代表两个通信端点
3.使用BroadcastChannel
同源脚本能够通过BroadcastChannel相互发送和接收消息
27.2.10 工作者线程数据传输
使用工作者线程时,要为它们提供某种形式的数据负载
在JS中,有三种在上下文之间转移信息的方式:
1.结构化克隆算法
可以用来在两个独立上下文间共享数据
该算法在后台实现,不能直接调用
支持的类型和注意事项: p805
2.可转移对象
可以把所有权从一个上下文转移到另一个上下文
只有下面几种对象是可以转移的:
ArrayBuffer
MessagePort
ImageBitmap
OffscreenCanvas
3.SharedArrayBuffer
既不克隆,也不转移,SharedArrayBuffer作为ArrayBuffer可以在不同浏览器上下文间共享
再把SharedArrayBuffer传给postMessage()时,浏览器只会传递原始缓冲区的引用
结果两个JS上下文会同时维护一个内存块的引用
27.3 共享工作者线程
共享工作者线程和共享线程与专用工作者线程类似,但可以被多个可以信任的执行上下文访问
27.3.1 创建共享工作者线程
和专用工作者线程类似,创建共享工作者线程主要通过加载JS文件
const sharedWorker = mew SharedWorker(location.href + 'emptySHaredWorker.js')
SharedWorker标识和独占
SharedWorker()只有在相同的标识不存在的情况下才会船舰新的实例
如果标识存在,则会和已有共享工作者线程建立连接
SharedWorker对象
SharedWorker()构造函数返回SharedWorker对象,被用作与新创建的共享工作者线程通信的连接点
SharedWorker支持 onerror 和 port 属性
SharedWorkerGlobalScope
SharedWorkerGlobalScope对 通过下面的属性和方法拓展了 WorkerGlobalScope
name: 可选的字符串标识符
importScripts(): 向工作者线程中导入任意数量的脚本
close(): 用于立即终止工作者线程
onconnect: 与共享线程建立新连接时,把其设置为处理程序
27.3.2 共享工作者线程生命周期
p815
27.3.3 连接到共享工作者线程
每次调用sharedWorker()构造函数,都会在共享线程内触发connect事件
27.4 服务工作者线程
服务工作者线程是一种类似浏览器中代理服务器的线程,可以拦截外出请求和缓存响应
这可以让网页在没有网络连接的情况下正常使用
27.4.1 服务工作者线程基础
服务工作者线程和专用工作者线程和共享工作者线程拥有许多共性:在独立上下文运行,只能异步消息通信
1.ServiceWorkerConatiner
服务工作者线程没有全局构造函数
服务工作者线程是通过ServiceWorkerConatiner来管理的,实例保存在navigator.serviceWorker属性中
2.创建服务工作者线程
和共享工作者线程类似,服务工作者线程也是在不存在时创建新实例,在存在时连接到已有实例
ServiceWorkerConatiner没有通过全局构造函数创建,而是暴露了register()方法,和Worker()一样接收一个脚本URL
navigator.serviceWorker.register('./emptyServiceWorker.js')
3.使用ServiceWorkerConatiner对象
ServiceWorkerConatiner接口是浏览器对服务工作者线程生态的顶部封装
支持以下事件处理程序:
oncontrollerchange
onerror
onmessage
支持下列新属性:
ready: 返回期约,解决未激活的ServiceWorkerRegistration对象
controller: 返回当前页面关联的激活的ServiceWorker对象,没有就返回null
register(): 接收URL和options对象创建或更新ServiceWorkerRegistration
getRegistrations(): 返回期约,解决为与ServiceWorkerContainer关联的ServiceWorkerRegistrations对象的数组
startMessage(): 开始传输通过Client.postMessage()派发的消息
4.使用ServiceWorkerRegistration对象
ServiceWorkerRegistration对象表示注册成功的服务工作者线程
可以在register()返回的解决期约的处理程序中访问到
调用navigator.service Worker.register()之后返回的期约会将注册成功的ServiceWorkerRegistration对象发送给处理函数
ServiceWorkerRegistration支持以下事件处理程序:
onupdatefound: 此事件会在开始安装新版本时触发
ServiceWorkerRegistration支持以下通用属性:
scope: 返回工作者线程域中的完整URL
navigationPreload: 返回注册对象相关的NavigationPreloadManger实例
pushManger: 返回注册对象相关的pushManger实例
installing: 有则返回状态为installing(安装)的服务工作者线程,否则为null
waiting: 有则返回状态为waiting(等待)的服务工作者线程,否则为null
active: 有则返回状态为active(活动)或activing的服务工作者线程,否则为null
ServiceWorkerRegistration支持以下方法:
getNotifications(): 返回期约,解决为Notification对象的数组
showNotifications(): 显示通知,可以配置title和options参数
update(): 直接从服务器请求新脚本,如果新脚本不同,则会重新初始化
unregister(): 取消服务工作者线程的注册
5.使用ServiceWorker对象
ServiceWorker对象可以通过两种方式获得:
ServiceWorkerContainer对象的controller属性
ServiceWorkerRegistration对象的active属性
ServiceWorker对象支持下面的事件处理程序和属性
onstatechange: 发送statechange事件会调用指定事件处理程序
scriptURL: 解析后注册服务工作者线程的URL
state: 表示当前状态
6.服务工作者线程的安全性
服务工作者线程API只能在安全上下文(HTTPS)中使用
在非安全上下文中,只豁免了 localhost 和 127.0.0.1 在本地加载的安全上下文
7.ServiceWorkerGlobalScope
在服务工作者线程内部,全局上下文是ServiceWorkerGlobalScope的实例
服务工作者线程可以通过self关键字访问该全局上下文
ServiceWorkerGlobalScope 对 WorkerGlobalScope的拓展:
chaches: 返回访问工作者线程的CacheStorage对象
clients: 返回访问工作者线程的Clients接口,用于访问底层的Client对象
registration: 返回访问工作者线程的ServiceWorkerRegistration对象
skipWaiting(): 强制服务工作者线程进入活动状态,要和Clients.claim()一起使用
fetch(): 在服务工作者线程内部发送常规网络请求
监听事件:
install: 进入安装状态时触发
activate: 在进入激活或者已激活状态触发
fetch: 在截获来自主页面的fetch()请求时触发
message: 通过postMessage()获取数据时触发
notificationclock: 用户点击了Service Worker Registration.showNotification()生成的通知时触发
notificationclose: 用户关闭或取消了了Service Worker Registration.showNotification()生成的通知时触发
push: 接收到推送消息时触发
pushsubscriptionchange: 在控制因素外导致推送订阅状态变化时触发
8.服务工作者线程作用域限制
服务工作者线程只能拦截其作用域内的客户端发送的请求
服务工作者线程的作用域实际上遵循了目录权限模型,只能相对于服务脚本所在路径缩小作用域
27.4.2 服务工作者线程缓存
在服务工作者线程之前,浏览器一直使用HTTP缓存
服务工作者线程的缓存是否简单:
服务工作者线程缓存不自动缓存任何请求
服务工作者线程缓存没有到期失效的概念
服务工作者线程缓存必须手动更新和删除
缓存版本必须手动管理
唯一的浏览器强制逐出策略基于服务工作者线程缓存占有的内存空间
本质上,服务工作者线程缓存是一个双层字典,顶级字典的条目映射到二级嵌套字典
顶级字典是一个CacheStorage对象,可以通过访问工作者线程全局作用域的caches属性访问到
1.CacheStorage对象
CacheStorage对象是映射到Cache对象的字符串键值对存储
CacheStorage的接口通过全局对象的caches属性暴露出来
CacheStorage的每个缓存可以通过给叉车是.open()传入响应字符串键取得
CacheStorage接口还有一个match()方法,可以根据Request对象搜索CacheStorage中所有的Cache对象
CacheStorage.match()可以接受一个Options配置对象
2.Cache对象
CacheStorage通过字符串映射到Cache对象
服务工作者线程缓存只考虑HTTP的GET请求
要填充Cache,可以使用下面三个方法:
put(request,response): 在键(Request对象或URL字符串)和值(Response对象)同时存在时用于添加缓存项
add(request): 在只有Request对象或URL时使用此方法发送fetch()请求,并缓存响应
addAll(requests): 在希望填充全部缓存时使用,接受URL或Request对象的数组
检索Cache的方法:
matchAll(request,options): 返回期约,期约解决为匹配缓存中Reqponse对象的数组
match(request,options): 返回期约,期约解决我匹配缓存中的Response对象
options对象支持的属性:
cacheName: 只会匹配Cache键位指定那个字符串的缓存值
ignoreSearch: 设置为true时,会在匹配URL时忽略查询字符串
ignoreMethod: 设置为true时,会在匹配URL时忽略请求查询的HTTP方法
ignoreVary: 设置为true时,会在匹配URL时忽略Vary头部
3.最大存储空间
使用StorageEstimate API可以近似的获取有多少空间可用,以及当前使用了多少空间
只在安全上下文可用
27.4.3 访问工作者线程客户端
访问工作者线程会使用Client对象跟踪关联的窗口,工作线程和服务工作者线程
服务工作者线程可用通过Clients接口访问这些Client对象,该接口暴露在全局上下文的self.clients属性上
Client对象的属性和方法:
id: 返回客户端的全局唯一标识符
type: 表示客户端类型的字符串
url: 客户端的URL
postMessage(): 向单个用户发送消息
openWindow(url): 在新窗口打开指定URL
claim(): 强制设置当前服务工作者线程以控制其作用域中所以客户端
27.4.4 服务工作者线程和一致性
访问工作者线程最终用途:让网页能够模拟原生应用程序
因此服务工作者线程必须版本更新
可以确保 代码一致性 和 数据一致性
为确保一致性,服务工作者线程生命周期会尽力防止有损一致性的行为,例如:
服务工作者线程提早失败
服务工作者线程激进更新
未激活服务工作者线程消极活动
活动的服务工作者线程粘连
27.4.5 服务工作者线程的生命周期
服务工作者线程有6中可能存在的状态:
已解析 安装中 已安装 激活中 已激活 已失效
上述状态的每次变化都会在ServiceWorker上触发stateChange事件
24.4.6 控制反转和服务工作者线程持久化
虽然专用工作者线程和共享工作者线程是有状态的,但是服务工作者线程是无状态的
服务工作者线程遵循控制反转模式且是事件驱动的
27.4.7 通过updateViaCache管理服务文件缓存
正常情况下,浏览器加载的所有JS资源会按照它们Cache-Control头部纳入HTTP缓存管理
因为服务脚本没有优先权,所有浏览器变化在缓存文件失效前接受更新的服务脚本
可以通过updateViaCache属性设置客户端对待服务脚本的方式
该属性可以是以下三个字符串值:
imports: 默认值,顶级服务器脚本永远不会被缓存,但是通过importScripts()在服务工作者线程内部导入的文件会按照Cache-Control头部设置纳入HTTP缓存管理
add: 服务脚本没有任何特殊待遇
none: 顶级服务脚本和通过importScripts()在工作者线程内部导入的文件永远不会被缓存
27.4.8 强制性服务工作者线程操作
在某些情况下,可能有必要让服务工作者线程尽快的进入已激活状态,但是可能会造成资源版本控制不一致
操作通常适合在安装事件中缓存资源,此时要强制让服务工作者线程进入活动状态,然后再强制活动服务工作者线程去控制关联的客户端
27.4.9 服务工作者线程消息
和专业工作者线程与共享工作者线程一样,服务工作者线程也能通过postMessage()和客户端交换信息
发送给服务工作者线程的信息可以在全局作用域处理,而发送回客户端的消息则可以在ServiceWorkerContext上处理
27.4.10 拦截fetch事件
服务工作者线程最重要的一个特性就是拦截网络请求
服务工作者线程中的网络请求会注册为fetch事件
它不仅可以拦截fetch()发送的请求,还能拦截JS,CSS,图片和HTML等资源发送的请求
如果想让服务器工作者线程模拟离线应用程序,他就必须能够把控页面正常运行的所有资源
让服务器能够决定处理fetch的事件方法是event.respondWith()
下面是几种网络缓存策略:
1.从网络返回
简单的转发fetch事件
2.从缓存返回
检查缓存,对于肯定有缓存的资源可以使用该策略
3.从网络返回,缓存做后备
把网络获取的最新数据作为首选,但如果缓存中有值也会返回缓存的值
4.从缓存返回,网络作为后备
优先考虑响应速度,但任然会在没有缓存的情况下发送网络请求
5.通用后备
应用程序需要考虑缓存和网络都不可用的情况
服务工作者线程可用在安装时缓存后备资源,然后在缓存和网络失败时返回他们
27.4.11 推送通知
对于模拟原生应用程序的Web应用程序而言,必须支持推送消息
有了服务工作者线程,可以实现网页能够接收服务器的推送事件,然后在设备上显示通知
为了在PWA应用中支持推送消息,必须支持以下四种行为:
服务工作者线程必须能够显示通知
服务工作者线程必须能够处理与这些通知的交互
服务工作者线程必须能够订阅服务器的推送通知
服务工作者线程必须能够在应用程序没打开的时候处理推送消息