🫧🫧🫧ServiceWorkerGlobalScope 让你重新认识 ServiceWorker

简介: ServiceWorkerGlobalScope和上两章的概念基本相同,都是继承自WorkerGlobalScope,但是ServiceWorkerGlobalScope 是在ServiceWorke

ServiceWorkerGlobalScope和上两章的概念基本相同,都是继承自WorkerGlobalScope,但是ServiceWorkerGlobalScope
是在ServiceWorker中使用的,所以它的属性和方法也是ServiceWorker中使用的。

ServiceWorkerGlobalScope

ServiceWorkerGlobalScopeServiceWorker的全局作用域,不同于之前讲的两个全局作用域,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对象有三个属性,分别是activeinstallingwaiting

这三个属性分别对应着三个状态,分别是activeinstallingwaiting,同时他们指向的也都是ServiceWorker对象;

通常情况这三个属性只有一个是有值的,而另外两个是null,这三个属性的值是根据ServiceWorker
的状态来决定的,如果想要获得ServiceWorker的状态,可以通过ServiceWorker.state来获取。

然后会有两个函数,分别是updateunregister,这两个函数分别用来更新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对象有四个函数,分别是enabledisablesetHeaderValuegetState

enabledisable分别用来开启和关闭ServiceWorker预加载,这个很简单,不多说;

setHeaderValue函数用来设置ServiceWorker预加载的头信息,这个函数的参数是一个字符串,这个字符串应该是一个字节序列;

其实这个函数和上的enabledisable函数一样的,不同的是上面默认设置头部信息值为truefalse,而这个函数可以自定义头部信息的值;

getState获取的就是一个预加载的状态信息,这个状态信息包括了enabledheaderValue
两个属性,分别表示预加载是否开启和预加载的头信息,和上面的enabledisablesetHeaderValue这些函数的使用情况息息相关。

那么这个具体有什么作用呢?其实预加载这个概念大家应该是都清楚的,不同的是这里的预加载指的是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更新的方式,其值可以是importsall
,默认值是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;
}

看到上面的这些属性和方法,发现之前基本上都是使用过的,而且有之前的基础也都很好理解,不理解的可以自己动手去试试看,这里就不多讲解了。

这里不认识的也就剩controlleroncontrollerchange了,其实这两个属性都是用来表示当前的ServiceWorker对象的,controllerServiceWorker对象,oncontrollerchangeServiceWorker对象的事件,用来表示ServiceWorker对象的状态发生变化时触发。

回到文章最开始客户端和服务端通讯的示例,之前使用的是navigator.serviceWorker.ready,这个会返回一个Promise,这个Promiseresolve的参数是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来处理一下readyPromise,这里就不做演示了。

总结

这篇从ServiceWorkerGlobalScope开始,详细的认识了ServiceWorkerGlobalScope和一些关联的对象和事件;

然后从ServiceWorkerGlobalScope讲到了ServiceWorker对象,发现ServiceWorker对象并不是注册ServiceWorker时使用的,引出了ServiceWorkerContainer对象;

ServiceWorkerContainer对象是navigator.serviceWorker,这个才是真实的注册ServiceWorker时使用的对象;

最后讲到了ServiceWorkerContainer对象的一些属性和方法,以及ServiceWorker对象的一些属性和方法,发现ServiceWorker对象的postMessage方法可以直接使用,不需要再通过ServiceWorkerRegistration对象来获取ServiceWorker对象了;

到这里才算是真正的认识了ServiceWorkerServiceWorker是前三个Worker中最复杂的一个,也是最重要的一个,因为它可以实现客户端和服务端的通讯,这也是ServiceWorker的最大的作用。

这里只是起点,并不是终点,Worker中还有很多API,例如之前讲到的Push APINotification APICache API等等,这些都是ServiceWorker的重要组成部分,所以这只是一个开始。

历史章节和预告

结语

目前Web Worker系列文章已经其实已经进入了尾声,这里也是原理的最后一篇了,后续的安排暂时还没有,目前的计划是:

  1. 继续深入Web Worker相关的API,例如Push APINotification APICache API等等;
  2. 找一些Web Worker的实际应用,来写一下案例;

目前就想到这两个方面,但是今年还有一个计划就是准备弄一个源码阅读的系列,准备从Vue3源码开始;

这个计划也是我考虑很久的一个计划,因为Web Worker系列其实我也没有真正的在实际项目中使用过,所以也没有什么实际的应用;

虽然文章的案例代码都是我亲自写的,也都运行通过,但是并没有实际项目经验和真实案例的支持,只是自己的一个兴趣使然;

所以准备着重的进攻一下自己的主要技术栈,也就是Vue,从源码开始,那么这个系列肯定就是往后排了,甚至可能直接结束;

当然以项目这个系列来说,其实原理也都深入过了,但是也没有那么深,对于使用肯定是没问题的,但是对于深度使用这些肯定是不够看的;

自己也是没有实战相关的经验,所以这一个系列只是教学的角度,所以没太深入,但是对于初学者来说,应该是足够的;

而源码阅读系列可以看我最近写的文章都是关于源码阅读的,也是受到掘金源码阅读活动的启发,这里非常感谢若川大佬源码阅读的活动,才让我有了这个想法;

这里就不再多说了,如果有想继续看Web Worker系列的,可以在评论区留言,我会根据大家的反馈来决定是否继续写下去;

目录
相关文章
|
1月前
|
算法 程序员
探寻技术之美:代码世界的奇妙旅程
在数字化时代,技术已经渗透到生活的方方面面,而作为程序员,我深深感受到了代码世界的奇妙之处。本文将带领读者一起探寻技术之美,感悟代码背后的精妙之处。
|
4月前
|
人工智能 数据格式 Python
每日一问-ChapGPT-20230308-关于技术与思考的问题
每日一问-ChapGPT-20230308-关于技术与思考的问题
每日一问-ChapGPT-20230308-关于技术与思考的问题
|
文字识别 算法 NoSQL
读书分享:《程序员修炼之道:通向务实的最高境界》的思想经验
相较于全书众多的干货笔记,这篇文章是个别思想经验的总结,希望和大家交流。 ETC;DRY不仅限于编码;维护一个项目概念列表;帮助业务方理解他想要什么;防御性编程;继承税;学会沟通;小实验
读书分享:《程序员修炼之道:通向务实的最高境界》的思想经验
|
人工智能 架构师 程序员
十年老友记 | @边城:恰当的编程是会产生幸福感的
十年老友记 | @边城:恰当的编程是会产生幸福感的
140 0
|
运维 安全 小程序
9102年程序员生存之道
很多朋友都说,IT是吃青春饭的行业。很少人能在35岁之后还呆在这个行业。在这个行业滚打多年后我,有时在想是不是就有必要开始认真考虑这个问题了?这还得从认识的1个哥们的故事说起。
9102年程序员生存之道
带你读《果壳中的5G:新网络时代的技术内涵与商业思维》前言
《果壳中的5G:新网络时代的技术内涵与商业思维》前言
|
移动开发 Java Linux
了解阿里云 追逐编程梦
1.通过老师了解阿里云“飞天加速计划.高校学生在家实践”活动 2.通过自己对阿里云服务器的使用。加深自己对编程的认识 3.自己使用后心得体会
|
人工智能 Java 程序员
当你完全认清程序员这个行业,你才能越走越远
程序员是一个具备长久生命力的职业 我大学刚毕业的时候,程序员还是一个年轻的职业,那个年代,我们都没见过35岁以上的程序员。
1568 0
《商业的本质》读后感
开篇  这是一本值得你多次翻阅的书籍,不管是从书籍的内容来说,还是从译者的翻译水平来说,都是非常完美的一本书。  这是一本让我感觉无从下手的一篇读后感,因为截止目前为止我还只是一个醉心于阅读各种开源代码的个体工程师,我没有团队,更没有团队管理经验,所以我不便发表过多的评论,但是从我个人角度来说,这里面提到的很多观点会让你的职业生涯收益。
1449 0

热门文章

最新文章