ServiceWorkerGlobalScope
和上两章的概念基本相同,都是继承自WorkerGlobalScope
,但是ServiceWorkerGlobalScope
是在ServiceWorker
中使用的,所以它的属性和方法也是ServiceWorker
中使用的。
ServiceWorkerGlobalScope
ServiceWorkerGlobalScope
是ServiceWorker
的全局作用域,不同于之前讲的两个全局作用域,ServiceWorkerGlobalScope
需要有一个状态来维持它的生命周期,这个生命周期也是之前讲ServiceWorker
时提到过。
而这些生命周期都是ServiceWorkerGlobalScope
的属性,它们都是只读的,所以我们不能直接修改它们的值,只能通过ServiceWorker
的生命周期来修改它们的值。
说到这些生命周期,同时也提到了属性,那么我们就来看一下ServiceWorkerGlobalScope
的属性。
属性
还是老样子通过函数签名来认识一下ServiceWorkerGlobalScope
的属性。
interface ServiceWorkerGlobalScope extends WorkerGlobalScope {
// 返回与 ServiceWorker 关联的 Client 对象
readonly clients: Clients;
// 返回当前 ServiceWorker 注册的 ServiceWorkerRegistration 对象
readonly registration: ServiceWorkerRegistration;
// 返回代表 ServiceWorker 的 ServiceWorker 对象
readonly serviceWorker: ServiceWorker;
// 允许 ServiceWorker 跳过注册等待阶段,直接进入激活状态
skipWaiting(): Promise<void>;
// 处在安装状态的 ServiceWorker 会触发该事件
readonly oninstall: EventHandler;
// 处在激活状态的 ServiceWorker 会触发该事件
readonly onactivate: EventHandler;
// 当发送网络请求时,会触发该事件,前提是该请求在 ServiceWorker 中被拦截
readonly onfetch: EventHandler;
// 当客户端向 ServiceWorker 发送消息时,会触发该事件
readonly onmessage: EventHandler;
// 当 ServiceWorker 向客户端发送消息时,消息体内容不合规范时,会触发该事件
readonly onmessageerror: EventHandler;
}
clients
clients
属性返回一个Clients
对象,这个对象是用来管理ServiceWorker
关联的Client
对象的,我们可以通过clients
对象来获取ServiceWorker
关联的Client
对象,也可以通过clients
对象来向Client
发送消息。
在讲SeviceWorker
时并没有介绍过Client
对象,这里我们就简单介绍一下Client
对象的使用。
- main.js
navigator.serviceWorker.register('./sw.js').then(registration => {
// 注册成功
console.log('注册成功');
});
navigator.serviceWorker.ready.then(registration => {
registration.active.postMessage('Hello from client');
});
navigator.serviceWorker.onmessage = event => {
console.log('收到来自服务端发送的消息:', event.data);
};
- sw.js
self.addEventListener('install', function (event) {
console.log('installing');
});
self.addEventListener('activate', function (event) {
console.log('Service worker activating...');
});
self.addEventListener('message', event => {
console.log('收到来自客户端发送的消息:', event.data);
// 获取 ServiceWorker 关联的 Client 对象
self.clients.matchAll().then(clients => {
clients.forEach(client => {
// 向 Client 发送消息
client.postMessage('Hello from service worker');
});
});
})
通过这个示例我们可以看到,ServiceWorker
可以通过clients
对象来获取Client
对象,也可以通过clients
对象来向Client
发送消息。
而Client
对象其实就是注册了ServiceWorker
的客户端实例。
registration
registration
属性返回一个ServiceWorkerRegistration
对象,这个对象是用来管理ServiceWorker
注册的,我们可以通过registration
对象来获取ServiceWorker
注册的信息,也可以通过registration
对象来更新ServiceWorker
。
ServiceWorkerRegistration
对象其实就是活动的ServiceWorker
的注册信息,它是直接继承自EventTarget
的,所以它也可以通过addEventListener
来监听事件。
registration
在🎊🎊🎊深入 ServiceWorker,消息推送,后台同步,一网打尽!
这一章中经常使用,所以就不再有代码示例,直接上函数签名。
interface ServiceWorkerRegistration extends EventTarget {
// ServiceWorkerRegistration 对象的唯一标识
readonly scope: string;
// ServiceWorkerRegistration 对象关联的 ServiceWorker 对象
readonly active: ServiceWorker;
// ServiceWorkerRegistration 对象关联的 ServiceWorker 对象
readonly installing: ServiceWorker;
// ServiceWorkerRegistration 对象关联的 ServiceWorker 对象
readonly waiting: ServiceWorker;
// ServiceWorkerRegistration 对象关联的 NavigationPreloadManager 对象
readonly navigationPreload: NavigationPreloadManager;
// 更新 ServiceWorkerRegistration 对象关联的 ServiceWorkerUpdateViaCache 对象
readonly updateViaCache: ServiceWorkerUpdateViaCache;
// 更新 ServiceWorker
update(): Promise<void>;
// 注销 ServiceWorker
unregister(): Promise<boolean>;
// 当 ServiceWorkerRegistration 对象关联的 ServiceWorker 对象状态发生变化时,会触发该事件
readonly onupdatefound: EventHandler;
}
从上面的函数签名可以看到,ServiceWorkerRegistration
对象有三个属性,分别是active
、installing
、waiting
;
这三个属性分别对应着三个状态,分别是active
、installing
、waiting
,同时他们指向的也都是ServiceWorker
对象;
通常情况这三个属性只有一个是有值的,而另外两个是null
,这三个属性的值是根据ServiceWorker
的状态来决定的,如果想要获得ServiceWorker
的状态,可以通过ServiceWorker.state
来获取。
然后会有两个函数,分别是update
和unregister
,这两个函数分别用来更新ServiceWorker
和注销ServiceWorker
。
最后还有一个事件onupdatefound
,这个事件会在ServiceWorkerRegistration
对象关联的ServiceWorker
对象状态发生变化时触发。
这些大家都可以自己去尝试一下,都很简单,这里最主要的是navigationPreload
属性和updateViaCache
属性。
navigationPreload
navigationPreload
属性返回一个NavigationPreloadManager
对象,这个对象是用来管理ServiceWorker
预加载的,我们可以通过navigationPreload
对象来开启ServiceWorker
预加载,也可以通过navigationPreload
对象来关闭ServiceWorker
预加载。
来看一下NavigationPreloadManager
对象的函数签名:
interface NavigationPreloadManager {
// 开启 ServiceWorker 预加载
enable(): Promise<void>;
// 关闭 ServiceWorker 预加载
disable(): Promise<void>;
// 设置 ServiceWorker 预加载头信息,默认值为`true`,该值应该是一个字节序列
setHeaderValue(value: string): Promise<void>;
// 获取一个 NavigationPreloadState 对象,该对象包含了 ServiceWorker 预加载的状态
getState(): Promise<NavigationPreloadState>;
}
type NavigationPreloadState = {
enabled: boolean;
headerValue: string;
};
从上面的函数签名可以看到,NavigationPreloadManager
对象有四个函数,分别是enable
、disable
、setHeaderValue
、getState
;
enable
和disable
分别用来开启和关闭ServiceWorker
预加载,这个很简单,不多说;
setHeaderValue
函数用来设置ServiceWorker
预加载的头信息,这个函数的参数是一个字符串,这个字符串应该是一个字节序列;
其实这个函数和上的enable
和disable
函数一样的,不同的是上面默认设置头部信息值为true
或false
,而这个函数可以自定义头部信息的值;
getState
获取的就是一个预加载的状态信息,这个状态信息包括了enabled
和headerValue
两个属性,分别表示预加载是否开启和预加载的头信息,和上面的enable
、disable
、setHeaderValue
这些函数的使用情况息息相关。
那么这个具体有什么作用呢?其实预加载这个概念大家应该是都清楚的,不同的是这里的预加载指的是ServiceWorker
中使用的资源的预加载,直接看示例:
addEventListener("activate", (event) => {
event.waitUntil(
self.registration.navigationPreload.enable()
);
});
addEventListener("fetch", (event) => {
event.respondWith(
event.preloadResponse.then((response) => {
if (response) {
return response;
}
return fetch(event.request);
})
);
});
上面的代码在讲ServiceWorker
的时候已经见过,没见过的就是navigationPreload.enable()
和preloadResponse
这两个属性;
navigationPreload.enable()
就是开启ServiceWorker
预加载,preloadResponse
就是获取预加载的资源,如果预加载的资源存在,就直接返回预加载的资源,如果预加载的资源不存在,就通过fetch
方法去获取资源;
这里其实还可以加上缓存的逻辑,这里只是为了演示navigationPreload
的使用,所以就没有加上缓存的逻辑。
updateViaCache
updateViaCache
属性是ServiceWorkerRegistration
对象的属性,用来设置ServiceWorker
更新的方式,其值可以是imports
或all
,默认值是imports
;
imports
表示只更新ServiceWorker
的脚本,不更新ServiceWorker
的资源;
all
表示更新ServiceWorker
的脚本和资源;
这个属性的作用就是用来设置ServiceWorker
更新的方式,如果设置为imports
,那么只会更新ServiceWorker
的脚本,不会更新ServiceWorker
的资源,如果设置为all
,那么ServiceWorker
的脚本和资源都会更新。
使用方式就是在注册ServiceWorker
的时候设置这个属性:
navigator.serviceWorker.register("sw.js", {
updateViaCache: "all"
});
ServiceWorker
上面已经介绍了ServiceWorkerGlobalScope
对象的属性和方法,这个时候再来看看ServiceWorker
对象的属性和方法,这个对象是ServiceWorkerGlobalScope
对象的属性,用来表示当前的ServiceWorker
对象,应该就会有不一样的感觉了。
还是先来看看ServiceWorker
对象的函数签名:
interface ServiceWorker extends EventTarget {
// scriptURL 是 ServiceWorker 的脚本地址
readonly scriptURL: string;
// state 是 ServiceWorker 的状态
readonly state: ServiceWorkerState;
// 当 service worker 的状态发生变化时触发
readonly onstatechange: ((this: ServiceWorker, ev: Event) => any) | null;
// postMessage 向 service worker 发送消息
postMessage(message: any, transfer?: Transferable[]): void;
}
这些在之前讲ServiceWorker
的时候都已经混了个眼熟,使用也很简单,看下面的示例:
if ("serviceWorker" in navigator) {
navigator.serviceWorker.register("sw.js").then((registration) => {
console.log('ServiceWorker registration successful with scope: ', registration.scope);
});
}
有没有发现一个问题,就是ServiceWorker
对象的函数签名并没有register
方法,但是在上面的示例中却可以使用register
方法,这是为什么呢?
因为ServiceWorker
对象并不是navigator.serviceWorker
对象,navigator.serviceWorker
对象是ServiceWorkerContainer
;
ServiceWorkerContainer
ServiceWorkerContainer
对象是navigator.serviceWorker
对象,用来表示ServiceWorker
容器,这个对象的函数签名如下:
interface ServiceWorkerContainer extends EventTarget {
// controller 是当前的 ServiceWorker
readonly controller?: ServiceWorker;
// ready 是一个 Promise,表示 ServiceWorker 是否准备好
readonly ready: Promise<ServiceWorkerRegistration>;
// register 注册 ServiceWorker
register(scriptURL: string, options?: RegistrationOptions): Promise<ServiceWorkerRegistration>;
// getRegistration 获取 ServiceWorkerRegistration
getRegistration(clientURL?: string): Promise<ServiceWorkerRegistration | undefined>;
// getRegistrations 获取所有的 ServiceWorkerRegistration
getRegistrations(): Promise<ServiceWorkerRegistration[]>;
// startMessages 启动 ServiceWorker 的消息监听
startMessages(): void;
// oncontrollerchange 当 ServiceWorker 的 controller 发生变化时触发
oncontrollerchange: ((this: ServiceWorkerContainer, ev: Event) => any) | null;
// onmessage 当 ServiceWorker 发送消息时触发
onmessage: ((this: ServiceWorkerContainer, ev: MessageEvent) => any) | null;
// onmessageerror 当 ServiceWorker 发送消息时发生错误时触发
onmessageerror: ((this: ServiceWorkerContainer, ev: MessageEvent) => any) | null;
}
看到上面的这些属性和方法,发现之前基本上都是使用过的,而且有之前的基础也都很好理解,不理解的可以自己动手去试试看,这里就不多讲解了。
这里不认识的也就剩controller
和oncontrollerchange
了,其实这两个属性都是用来表示当前的ServiceWorker
对象的,controller
是ServiceWorker
对象,oncontrollerchange
是ServiceWorker
对象的事件,用来表示ServiceWorker
对象的状态发生变化时触发。
回到文章最开始客户端和服务端通讯的示例,之前使用的是navigator.serviceWorker.ready
,这个会返回一个Promise
,这个Promise
的resolve
的参数是ServiceWorkerRegistration
对象;
然后使用ServiceWorkerRegistration
对象的active
属性获取ServiceWorker
对象,然后使用ServiceWorker
对象的postMessage
方法发送消息;
现在完全可以使用navigator.serviceWorker.controller
获取ServiceWorker
对象,然后使用ServiceWorker
对象的postMessage
方法发送消息,这样就方便很多了。
- main.js 修改如下:
navigator.serviceWorker.register('./sw.js').then(registration => {
console.log(registration);
});
navigator.serviceWorker.controller.postMessage('Hello from client');
navigator.serviceWorker.onmessage = event => {
console.log('收到来自服务端发送的消息:', event.data);
};
这样就可以直接使用SeviceWorker
对象的postMessage
方法发送消息了;
当然也是需要注意SeviceWorker
对象的状态,只有在active
状态才能发送消息,所以ready
的还是靠谱一些的;
不过也可以使用await
来处理一下ready
的Promise
,这里就不做演示了。
总结
这篇从ServiceWorkerGlobalScope
开始,详细的认识了ServiceWorkerGlobalScope
和一些关联的对象和事件;
然后从ServiceWorkerGlobalScope
讲到了ServiceWorker
对象,发现ServiceWorker
对象并不是注册ServiceWorker
时使用的,引出了ServiceWorkerContainer
对象;
ServiceWorkerContainer
对象是navigator.serviceWorker
,这个才是真实的注册ServiceWorker
时使用的对象;
最后讲到了ServiceWorkerContainer
对象的一些属性和方法,以及ServiceWorker
对象的一些属性和方法,发现ServiceWorker
对象的postMessage
方法可以直接使用,不需要再通过ServiceWorkerRegistration
对象来获取ServiceWorker
对象了;
到这里才算是真正的认识了ServiceWorker
,ServiceWorker
是前三个Worker
中最复杂的一个,也是最重要的一个,因为它可以实现客户端和服务端的通讯,这也是ServiceWorker
的最大的作用。
这里只是起点,并不是终点,Worker
中还有很多API
,例如之前讲到的Push API
,Notification API
,Cache API
等等,这些都是ServiceWorker
的重要组成部分,所以这只是一个开始。
历史章节和预告
- 🎉🎉🎉 Web Workers 使用秘籍,祝您早日通关前端多线程!
- 🥳🥳🥳Worker中还可以创建多个Worker,打开多线程编程的大门
- ✨✨✨ ServiceWorker 让你的网页拥抱服务端的能力
- 🎊🎊🎊深入 ServiceWorker,消息推送,后台同步,一网打尽!
- 🚂🚂🚂 ServiceWorker -> PWA的基石,在线离线都能玩!
- 💞💞💞SharedWorker 让你多个页面相互通信
- 🎁🎁🎁详解 Web Worker,不再止步于会用!
- [🎈🎈🎈SharedWorkerGlobalScope 居然只是看起来复杂][https://juejin.cn/post/7181667535577120824]
- 当前章节
- 构思中...
结语
目前Web Worker
系列文章已经其实已经进入了尾声,这里也是原理的最后一篇了,后续的安排暂时还没有,目前的计划是:
- 继续深入
Web Worker
相关的API
,例如Push API
,Notification API
,Cache API
等等; - 找一些
Web Worker
的实际应用,来写一下案例;
目前就想到这两个方面,但是今年还有一个计划就是准备弄一个源码阅读的系列,准备从Vue3
源码开始;
这个计划也是我考虑很久的一个计划,因为Web Worker
系列其实我也没有真正的在实际项目中使用过,所以也没有什么实际的应用;
虽然文章的案例代码都是我亲自写的,也都运行通过,但是并没有实际项目经验和真实案例的支持,只是自己的一个兴趣使然;
所以准备着重的进攻一下自己的主要技术栈,也就是Vue
,从源码开始,那么这个系列肯定就是往后排了,甚至可能直接结束;
当然以项目这个系列来说,其实原理也都深入过了,但是也没有那么深,对于使用肯定是没问题的,但是对于深度使用这些肯定是不够看的;
自己也是没有实战相关的经验,所以这一个系列只是教学的角度,所以没太深入,但是对于初学者来说,应该是足够的;
而源码阅读系列可以看我最近写的文章都是关于源码阅读的,也是受到掘金源码阅读活动的启发,这里非常感谢若川大佬源码阅读的活动,才让我有了这个想法;
这里就不再多说了,如果有想继续看Web Worker
系列的,可以在评论区留言,我会根据大家的反馈来决定是否继续写下去;