原创 袁亮 / 叫叫技能团队

引言

随着互联网的迅猛发展,网络运用正变得越来越强壮和杂乱。但是,网络连接的不可靠性和约束性依然是用户面临的常见应战之一。Service Worker 的呈现不仅为开发者供给了更大的灵活性和操控权,也为用户带来了更快、更可靠的网络体会。不管您是一个开发者、一个技能爱好者,本文都将协助您了解 Service Worker 基础知识及运用场景。


本文将围绕如下几点展开讲解

  • Service Worker 的基本原理是什么?
  • Service Worker 有哪些运用场景?
  • Service Worker 是怎么运用的?

先体会

  1. 翻开梦可宝图鉴
  2. 确任页面加载完结或显现 Ready to work offline 后关闭
  3. 断开网络连接从头翻开页面发现网页正常作业并且是秒开

这便是运用 Service Worker 在离线拜访场景运用下达到的作用

介绍

Service Worker 本质上充当 Web 运用程序、浏览器与网络(可用时)之间的代理服务器。Service Worker 运转在一个独立的线程中,独立于网页运转,能够在后台处理网络恳求,缓存资源,并满意像离线拜访、推送告诉等高档功用的需求。

揭秘 Service Worker 技术的奥秘

发生背景

Service Worker 技能的呈现源于 Web 运用程序对功用和用户体会不断提升的要求,以及旧有技能在完结离线拜访、推送告诉和高效网络恳求等方面的局限性。 在传统的 Web 运用程序中,客户端需求频频地与服务器通讯,然后导致网络推迟和呼应时间较长的问题。一起,在断网的情况下,Web 运用程序无法正常作业,这也约束了 Web 运用程序的运用场景和可用性。 为了处理这些问题,开发者们在 HTML5 标准化的过程中引入了离线缓存、Web Workers 等技能。虽然这些技能现已在必定程度上进步了 Web 运用程序的可用性和功用,但它们仍存在一些约束:

  • 离线缓存需求额外的代码和装备来完结,并且在离线情况下缓存的数据也不能主动更新。
  • Web Workers 虽然能够运用多线程加速运用程序的处理速度,但它们无法在后台运转,且不能在不同的线程之间进行通讯。

针对这些局限性,Service Worker 技能的呈现填补了这一空白。Service Worker 能够运用多线程处理大量的 data,能够缓存运用程序的资源,在离线情况下也能正常作业,并且能够处理推送告诉、优化网络恳求等使命。这些才能使得 Service Worker 技能成为构建高功用、高可用性 Web 运用程序的重要东西之一。

Cache简略介绍

Cache API 是专门为 Service Workers 设计的,用于缓存网络恳求和呼应的异步储存计划。

MDN 介绍中 Cache 是一个试验中的功用,在低版别浏览器中或许无法正常作业留意兼容性问题。或许引入 Cache Api polyfill

揭秘 Service Worker 技术的奥秘

图:Cache API 是以 Requst 做为 keyResponse 做为 value 进行存储的

Cache 供给一个 Request -> Response 的持久缓存,除非显式删去,存储在 Cache 里面的数据不会主动过期,一起也不会主动去更新,需求手动保护其更新。

Cache 基础用法

存入Cache Storage

经过称号标识获取一个Cache方针

// 经过一个 cacheName 来获取对应的缓存方针
const cache = await caches.open('hello-cache-v1');

将呼应内容(Response)存入指定的缓存中,add()addAll() 办法都是将回来的Response方针增加到 Cache 中,可是关于更杂乱的操作引荐运用 put() 办法,详细差异能够参考 Cache api

// 恳求一个txt文本资源
const request = new Request("/static/pre_fetched.txt", { method: "GET" });
fetch(request).then((response) => {
  // 成功后能够经过 Cache.put 办法将缓存设置进去
  caches.open("hello-cache-v1").then((cache) => {
    cache.put(request, response);
  });
});

能够看到/static/pre_fetched.txt文本内容已被存进缓存

揭秘 Service Worker 技术的奥秘

获取上次存储的 Response

运用 match 办法匹配一个 request 对应的 response, 假如匹配到直接回来命中的 Respose,未匹配则回来 undefined

// 再次恳求pre_fetched.txt文本内容
const request = new Request("/static/pre_fetched.txt", { method: "GET" });
caches.open("hello-cache-v1").then((cache) => {
  // 获取上次存储的 Response
  cache.match(request).then((matchResponse) => {
    matchResponse.text().then((txt) => {
      console.log(txt);
    });
  });
});

打印结果

揭秘 Service Worker 技术的奥秘

手动更新

能够看到将 pre_fetched.txt 中文本内容更新,再次获取文本内容输出的始终是 Cache Storage 中内容,体现了 Cache 不会主动更新的特色,假如要更新能够再次运用 cache.put() 办法更新。

caches.open("hello-cache-v1").then((cache) => {
	cache.put(request, response);
}

手动删去

caches.open("hello-cache-v1").then(function (cache) {
  cache.delete("/static/pre_fetched.txt").then(function (response) {
    console.log("done");
  });
});

Service Worker

简略介绍了Cache Api回归正题咱们先从怎么注册一个Service Worker开端

注册

Service Worker 被装置之前,首要需求在HTML页面内的脚本里注册 server worker 的脚本,经过 register 办法即可注册一个 Service Worker,承受 2 个参数 scriptURLoptions(可选)

留意:一个页面只答应注册一个Service Worker

if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('sw.js')
  .then(function(registration) {
    // success
  }).catch(function(error) {
    // error
  });
} else {
  // The current browser doesn't support service workers.
}

成功注册一个Service Worker

揭秘 Service Worker 技术的奥秘

作用规模Scope

options 选项中承受一个 scope,定义 service worker 注册规模的URL,表明 Service Worker 操控规模的 URL 子集。例如,假如你将 scope 设置为 /app/,那么 Service Worker 只会操控以 /app/ 开头的 URL。假如你没有供给 scope,那么默许的作用规模将是 Service Worker 脚本 URL 的目录。 例如:路径是 /sw.js 的 Service Worker,其作用规模默许是 /** ,因此 /** 下面的页面(如 /index.html)能够被 Service Worker 操控,假如我将 scope 调整为 /subdir 那么 index.html 中的资源恳求,发送 postMessage 音讯等都无法被 Service Worker 操控。

生命周期

揭秘 Service Worker 技术的奥秘

  1. Installing: 这是 Service Worker 生命周期的第一个阶段。在这个阶段,Service Worker 脚本被下载和装置。假如 Service Worker 脚本是第一次被注册,或许现已注册的 Service Worker 脚本发生了改动,那么就会触发装置过程。假如在装置过程中没有发生过错,那么** **Service Worker 将进入 installed 状况。
  2. Installed: 在这个阶段,Service Worker 现已被成功装置,可是还没有激活。假如当前现已有一个激活的 Service Worker,那么新装置的 Service Worker 将会进入等候状况,直到一切翻开的页面都不再运用旧的 Service Worker。
  3. Activating: 当没有页面在运用旧的 Service Worker 时,新的 Service Worker 将进入激活阶段。在这个阶段,你能够履行一些更新操作,例如整理旧版别的 Service Worker 缓存的资源。假如在激活过程中没有发生过错,那么 Service Worker 将进入 activated 状况。
  4. Activated: 在这个阶段,Service Worker 现已被激活,并且能够操控页面。Service Worker 能够开端处理 fetchmessage事情,阻拦和处理网络恳求,以及与页面进行通讯。
  5. Redundant: Service Worker 或许会由于多种原因进入 redundant 状况。例如,假如在装置或激活过程中发生过错,或许有一个新的 Service Worker 取代了它,那么它将进入 redundant 状况。进入这个状况的 Service Worker 将不再操控页面,也不会再接收到事情。

这些状况改动的过程是彻底由浏览器主动办理的,可是你能够经过在 Service Worker 脚本中增加事情监听器,来在每个状况改动时履行自定义的代码。例如,你能够在 installactivate 事情的监听器中增加缓存办理的代码,或许在 fetch 事情的监听器中增加恳求处理的代码。

生命周期事情

Service Worker 中的主要生命周期阶段的改变,是经过事情来告诉脚本的,所以 Serice Worker 的脚本主要需求做的是为不同生命周期事情绑定好对应的处理器。核心的生命周期事情处理器如图示:

揭秘 Service Worker 技术的奥秘

装置事情 (Install)

在下载事情被浏览器呼应完结后,装置事情是 Service Worker 生命周期中第一个被触发的,所以它十分重要。咱们一般运用它来完结各种初始化使命,主要是资源的预加载、缓存以及持久化一些长期有效的状况。 监听到装置事情就能够预先拉取一些资源,并在运用缓存中存储它们并用给定的姓名标识,拜见下面的代码示例

const CACHE_NAME = "cache-v1";
const urlsToCache = ["/static/pre_fetched.txt", "/static/pre_fetched.html"];
// 监听装置事情
self.addEventListener("install", (event) => {
  event.waitUntil(
    caches.open(CACHE_NAME).then(function (cache) {
      console.log("Opened cache");
      return cache.addAll(urlsToCache);
    })
  );
});

激活事情 (activate)

当 Service Worker 现已就绪,能够操控页面恳求并处理功用事情如 fetchpushsync,激活事情就会被触发,咱们监听激活事情的处理器就能够呼应。

self.addEventListener('activate', event => {
  console.log("activate event");
});

激活事情标志着从这之后的恳求将被 Service Worker 接收,所以一般用于区分 Service Worker 接收前后的分隔。

恳求事情 (fetch)

fetch 事情被触发于页面对网络发起任意恳求,看一个简略示例,Service Worker 绑架 fetch 恳求替换图片

const CACHE_NAME = "cache-v1";
const urlsToCache = ["/static/jojo.png"];
// 监听装置事情
self.addEventListener("install", (event) => {
  event.waitUntil(
    caches.open(CACHE_NAME).then(function (cache) {
      // 存入Cache
      return cache.addAll(urlsToCache);
    })
  );
});
// fetch事情
self.addEventListener("fetch", (event) => {
  console.log(`Handling fetch event for ${event.request.url}`);
  var requestUrl = new URL(event.request.url);
  // 改写呼应,从Cache中回来jojo.png
  if (requestUrl.pathname === "/static/pig.png") {
    event.respondWith(caches.match("/static/jojo.png"));
  }
});
  • 在装置事情中 Service Worker 恳求了 /static/jojo.png 并加入缓存。
  • 而在 fetch 事情中,咱们绑架了网络恳求,当资源方针为 /static/pig.png 时,service worker 没有转发恳求,而是从缓存列表取出 /static/jojo.png,呼应给了页面达到了图片绑架替换的作用。

第一次拜访

揭秘 Service Worker 技术的奥秘
再次拜访时
揭秘 Service Worker 技术的奥秘

sync事情

Service Worker 的 sync事情用于在用户设备从头连上网络时履行某些使命。这在用户离线时保存数据,然后在用户从头上线时进行网络恳求的场景中特别有用。

当用户设备离线时,你能够在 Service Worker 中运用 SyncManager 接口的 register 办法注册一个同步使命:

self.addEventListener('fetch', event => {
  if (!navigator.onLine) {
    event.waitUntil(
      // 注册一个名为 'myFirstSync' 的同步使命
      self.registration.sync.register('myFirstSync')
    );
  }
});

然后,你能够在 Service Worker 中监听 sync 事情,并在事情处理函数中履行网络恳求:

self.addEventListener('sync', event => {
  if (event.tag === 'myFirstSync') {
    event.waitUntil(
      // 在这里履行网络恳求
      fetch('...')
    );
  }
});

push事情

Service Worker 中的 push 事情是在接收到推送告诉时触发的事情。当 Service Worker 接收到推送告诉时,会触发 push 事情,以便在后台处理该告诉。

恳求用户的告诉权限

// 恳求告诉权限
Notification.requestPermission()
.then(function(permission) {
  if (permission === 'granted') {
    console.log('Notification permission granted.');
  } else {
    console.log('Unable to get permission to notify.');
  }
});

推送一条push音讯

揭秘 Service Worker 技术的奥秘
Service Worker接收到 push 音讯并告诉用户

self.addEventListener("push", function (event) {
  const data = event.data.json();
  console.log("[Service Worker] Push Received.", data);
  const options = {
    body: data.body,
    icon: data.icon
  };
  event.waitUntil(self.registration.showNotification(data.title, options));
});

message事情

message 事情是用于通讯的,处理从页面(或其他 Service Worker)发送来的音讯

// 通讯音讯
self.addEventListener("message", (event) => {
  console.log("Received a message from page: ", event.data);
});

与烘托进程通讯

Service Worker 能够经过 postMessage 与烘托进程(一般是浏览器的主线程,也便是运转你的网页的当地)进行通讯

Service Worker 向烘托进程发送音讯: Service Worker 能够经过 clients.matchAll 办法获取一切受其操控的客户端(Client 方针代表了一个由 Service Worker 操控的页面),然后运用 postMessage 办法向其间的一个或多个客户端发送音讯。

self.addEventListener('activate', event => {
  // 发送音讯到客户端
  event.waitUntil(clients.matchAll().then(all => {
    all.forEach(client => {
      client.postMessage('Hello from the service worker');
    });
  }));
});

烘托进程接收音讯: 在烘托进程(页面)中,你能够监听 message 事情来接收 Service Worker 发送的音讯。

navigator.serviceWorker.addEventListener('message', event => {
    console.log('Received a message from service worker: ', event.data);
});

烘托进程向 Service Worker 发送音讯: 在烘托进程中,你能够经过 Service Worker 特点获取 Service Worker 注册方针,然后运用 postMessage 办法向 Service Worker 发送音讯。

navigator.serviceWorker.controller.postMessage('Hello from the page');

Service Worker 接收音讯: 在 Service Worker 中,你能够监听 message 事情来接收页面发送的音讯。

self.addEventListener('message', event => {
    console.log('Received a message from page: ', event.data);
});

更新Service worker

下面是 Service worker 更新流程

  1. 更改 Service Worker 脚本: 当你需求更新 Service Worker 时,首要需求修正 Service Worker 脚本的内容。这或许包括增加新的事情处理函数,修正缓存战略等。
  2. 浏览器查看更新: 每次页面加载时,浏览器都会查看 Service Worker 脚本是否有更新(比方脚本内容发生改变)。这个查看是字节等级的,所以即便只是脚本中的一个字符发生了改变,浏览器也会以为 Service Worker 脚本现已更新。
  3. 装置新的 Service Worker: 假如浏览器检测到 Service Worker 脚本有更新,那么它会开端装置新的 Service Worker。新的 Service Worker 将进入 installing状况,并触发install 事情。
  4. 新的 Service Worker 进入等候状况: 装置完毕后,新的 Service Worker 会进入 waiting 状况,等候旧的 Service Worker 释放对当前页面的操控权。
  5. 激活新的 Service Worker: 当旧的 Service Worker 不再操控任何页面时,新的 Service Worker 将进入 activating 状况,并触发 activate 事情。在这个阶段,你能够整理旧版别的 Service Worker 缓存的资源。
  6. 新的 Service Worker 开端操控页面: 当新的 Service Worker 彻底激活后,它将开端操控页面,处理 fetchmessage 事情。

需求留意的是,假如你期望新的 Service Worker 在页面下次加载时当即生效,能够在 Service Worker 脚本中调用 self.skipWaiting() 办法。这将越过 waiting 状况,直接激活新的 Service Worker。相同,假如你期望新的 Service Worker 在激活后当即开端操控页面,能够调用 clients.claim() 办法。 另外,假如在开发过程中需求频频更新 Service Worker,你或许需求在浏览器的开发者东西中启用 “Update on reload” 选项,这将在每次页面从头加载时强制更新 Service Worker。

揭秘 Service Worker 技术的奥秘

常见缓存战略

Service Worker 能够完结多种缓存战略,下面是一些常见的战略:

  1. Cache First(缓存优先): 这种战略首要测验从缓存中获取恳求的资源。假如缓存中有对应的资源,那么直接运用缓存中的资源;假如缓存中没有对应的资源,那么向网络发送恳求。这种战略合适用于那些不常常改变的资源,例如 CSSJavaScript 文件或许图片等。

    揭秘 Service Worker 技术的奥秘

  2. Network First(网络优先): 这种战略首要测验向网络发送恳求。只有当网络恳求失利(例如网络不可用)时,才会从缓存中获取资源。这种战略合适用于那些需求实时更新的资源。

    揭秘 Service Worker 技术的奥秘

  3. Cache Only(只用缓存): 这种战略只运用缓存中的资源,不向网络发送恳求。假如缓存中没有对应的资源,那么恳求失利。这种战略合适用于离线运用。

    揭秘 Service Worker 技术的奥秘

  4. Network Only(只用网络): 这种战略只向网络发送恳求,不运用缓存。这种战略合适用于那些无法被缓存的资源。

    揭秘 Service Worker 技术的奥秘

  5. Stale While Revalidate(先用缓存,后台更新): 这种战略首要从缓存中获取资源并回来,然后在后台向网络发送恳求,更新缓存。这种战略能够快速呼应恳求,一起保证缓存中的资源是最新的。

    揭秘 Service Worker 技术的奥秘

以上都是常见的缓存战略,但在实际运用中,或许需求依据详细的运用需求,选择合适的战略,或许组合运用多种战略。例如,关于 CSSJavaScript 文件,或许运用 Cache First 战略;而关于新闻或许文章,或许运用 Network First 或许 Stale While Revalidate 战略。

兼容性

Service Worker是现代浏览器的一个高档特性,它依赖于 fetch APICache ApiPromise 等。能够看出 Service Worker 在干流浏览器中现已得到了较好的支撑,但仍有一些浏览器版别存在兼容性问题,如 Internet ExplorerSafari 10 或更早的版别。

揭秘 Service Worker 技术的奥秘
运用Service Worker的必要条件:

  • 浏览器支撑(在这里测验你的浏览器是否支撑)
  • 必须运用 HTTPS

留意事项

  • 出于安全考量,Service worker 只能由 HTTPS 承载,毕竟修正网络恳求的才能露出给中间人攻击会十分风险,假如答应拜访这些强壮的 API,此类攻击将会变得很严重
  • Service Worker 是独立于烘托上下文的独立线程,所以它们都是无法直接操作 DOM 或许 window 方针的。假如咱们有和其他 Worker 或许页面交互的需求,能够运用 postMessagemessage 事情来进行进程/线程间通讯
  • 在 Service Worker 中运用 fetch API 来转发恳求,恳求中默许不会包含 cookie 等中的用户认证信息。假如需求为转发恳求附带认证信息,在 fetch 恳求中增加 credentials 的参数:
fetch(url, {
  credentials: 'include'
})
  • 假如方针资源支撑 CORS,在构建恳求需求附带参数 {mode: 'cors'}
  • 30XHTTP 状况码尚不支撑离线恳求重定向,这是一个已知的issue。建议在官方支撑离线重定向前,依据你的运用场景寻觅其他计划
  • 在运用 Service Worker 代理HTTP的呼应体时,务必记住 clone response,而不要直接消费掉呼应体。 原因是 HTTP response 是一个流,它的内容只能被消费一次。 只需咱们依然期望既能让浏览器正确的获得呼应体中的内容,又能是它被缓存或许在 Service Worker 作内容查看,请不要忘掉仿制一个呼应体。

运用场景

  1. 全静态站点 假如一个网站只包含静态数据而无需服务,咱们能够缓存一切的 html 页面,css 款式,脚本和图片等资源,来使得这个页面在初次翻开后能够被彻底地离线拜访。例如前面说到的宝可梦图鉴
  2. 预加载 为了优化首屏烘托,页面上非必要的资源一般被推迟加载直到它们被需求。这类资源运用Server Worker来加载既能够使得在需求被加载时有杰出的体会,又不会影响到首屏功用。 Demo / Demo prefetch video
  3. 应变呼应 有时候 HTTP 恳求或许会由于不确定因素失利(如服务器离线,网络中止等),此刻为用户供给一个应变的呼应比方展现上一次成功加载的资源/数据。 Service worker 能够协助验证恳求是否成功,假如失利就供给应变战略的回复。 Demo
  4. 仿制呼应 仿制呼应是十分有用。它能够协助咱们阻隔部分特定的恳求来运用给定的回复,或许咱们能够用它来测验一些尚不可用,或许不能稳定重现问题的资源或许 REST API.
  5. 窗口缓存 Service Worker 来承担缓存数据的职责,页面能够直接运用 window.cache 来拜访缓存。 经过窗口缓存作为媒介能够直接完结 service worker 向页面的数据传递,也能够将 Service Worker 用作缓存的生产者而页面作为顾客。

更多运用场景能够参考官方示例:googlechrome.github.io/samples/ser…

价值

运用 Service Worker 技能构建 Web 运用程序,能够带来以下价值:

  • 离线拜访: Service Worker 能够将 Web 运用程序离线缓存,使得用户在离线情况下依然能够拜访运用程序,大大进步了用户体会和可用性。
  • 高功用和低推迟: Service Worker 能够缓存资源和呼应恳求,然后降低网络推迟和进步运用程序的功用。
  • 推送告诉: Service Worker 能够以后台模式运转并接收推送告诉音讯,为用户供给实时的更新和信息推送。
  • 更好的用户体会: Service Worker 能够进步运用程序的呼应速度和功用,并完结像离线拜访、推送告诉等功用,然后提升了运用程序的用户体会。

合理优化和运用 Service Worker 技能,能够为 Web 运用程序带来更好的用户体会和更高的功用体现。

结语

最终们能够得出一些要害的结论: 首要 Service Worker 是一种强壮的网络技能,它能够使开发人员在用户的浏览器中运转后台脚本,供给离线体会,以及体系告诉等功用。 其次,经过阻拦和处理网络恳求,Service Worker 能够有效地创立可靠的功用体会,即便在网络不稳定或彻底离线的情况下也能如此。 但是,也有一些应战,例如更新 Service Worker 的杂乱性,需求制定明智的缓存战略,以及有些浏览器的兼容性问题等。总的来说,虽然 Service Worker 技能有其应战,但其供给的丰富功用和优化用户体会的才能使其在 Web 开发中具有较大的潜力。

参考文献

developer.mozilla.org/zh-CN/docs/…
developer.mozilla.org/zh-CN/docs/…
github.com/GoogleChrom…
googlechrome.github.io/samples/ser…
developer.chrome.com/docs/workbo…
/post/699690…