网站首页 > 博客文章 正文
大家好,很高兴又见面了,我是"高级前端?进阶?",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发!
1. 什么是 Web Worker
一直阻碍 JavaScript 的因素实际上是语言本身,JavaScript 是一种单线程环境,即多个脚本无法同时运行。例如:处理界面事件、查询和处理大量 API 数据以及操作 DOM。
开发者虽然可以使用 setTimeout()、setInterval()、XMLHttpRequest 和事件处理脚本等技术来模拟 “并发”。虽然功能是异步的,但不阻塞不一定就意味着并发,系统会在生成当前执行脚本后处理异步事件。
Web Worker 规范定义了一个用于在 Web 应用中生成后台脚本的 API。借助 Web Workers,开发者可以执行一系列操作,例如:触发长时间运行的脚本来处理计算密集型任务,同时不会阻止界面或其他脚本处理用户互动。
Worker 利用类似线程的消息传递实现并行,非常适合用来保持界面刷新、性能和响应速度。同时,Service Worker 还可以嵌入到 PWA 中,使应用程序能够离线运行,此时 Service Worker 相当于扮演了某种虚拟代理。
但是值得一提的是,Web Worker 无权访问 DOM,因此无法更改 Web 应用程序的外观。
2.Service Worker 本质是 Web Worker
Service worker 本质上充当 Web 应用程序、浏览器与网络(可用时)之间的代理服务器。这个 API 旨在创建有效的离线体验,其会拦截网络请求并根据网络是否可用来采取适当的动作、更新来自服务器的的资源,其还提供入口以访问推送通知(Push Notification)和后台同步 API(Background Sync API)。
- Push API:给与 Web 应用程序接收从服务器发出的推送消息的能力,无论 Web 应用程序是否在用户设备前台,甚至刚加载完成。这样,开发人员就可以向用户投放异步通知和更新,从而让用户能更及时地获取新内容。
- Background Synchronization API: 使 Web 应用程序能够推迟任务,以便一旦用户拥有稳定的网络连接就可以在 Service Worker 中运行。
其实,为 Service Worker 编写代码时,很多都是信手拈来,比如:可以使用 JavaScript 语言、可以像监听界面事件一样监听生命周期事件、可以像往常一样使用 Promise 来管理控制流。但是,有时 Service Worker 的行为会导致开发者不知所措,比如:当刷新页面却没有看到代码更改实时生效。
与传统 Web 应用程序架构相比,Service Worker 相当于构造了第三、即中间层,该层用于控制和操作客户端和服务器之间发送的所有数据。
开发者还可以将 Service Worker 视为一种浏览器扩展程序,即网站可以安装在用户浏览器中的一种扩展程序。安装后,Service Worker 会通过强大的中间层扩展网站的浏览器,Service Worker 层可拦截和处理网站发出的所有请求。
同时,Service Worker 层还有自己的生命周期,独立于浏览器标签页。简单的页面刷新不足以更新 Service Worker,同时每个层都有自己唯一的更新规则。
3.Service Worker 能力强大但依然有限
在 Web 上使用 Service Worker 可以带来诸多好处,比如:
- 即使用户处于离线状态也可以顺利运行
- 通过缓存大幅提升性能
- 允许使用推送通知
- 允许作为 PWA 安装
注意:即使用户离开网站或关闭标签页,活跃的 Service Worker 仍会继续工作。浏览器会保留此 Service Worker,以便其在下次用户返回网站时做好准备。在发出第一个请求之前,Service Worker 将有机会拦截该请求并控制页面,此时网站甚至可以离线工作,即 Service Worker 可以提供网页的缓存版本,即使用户没有连接到互联网也是如此。
但是,Service Worker 虽然强大也依然受到设计的限制,比如:无法进行同步,更不能与网站在同一会话中执行任何操作,这也意味着 Service Worker 无法访问以下对象。
- localStorage
- DOM
- 窗口
但是,网页可以通过多种方式与其 Service Worker 通信,包括: postMessage、一对一消息通道(MessageChannel)和一对多广播(BroadcastChannel)通道。
下面是 Service Worker 中使用 postMessage 通信的示例:
navigator.serviceWorker.register("service-worker.js");
navigator.serviceWorker.ready.then((registration) => {
registration.active.postMessage(
"Test message sent immediately after creation",
);
});
如果是 MessageChannel 可以通过下面的方式:
var messageChannel = new MessageChannel();
messageChannel.port1.addEventListener('message', replyHandler);
worker.postMessage(data, [messageChannel.port2]);
function replyHandler (event) {
console.log(event.data);
}
// 下面是 Service Worker 中代码
self.addEventListener('message', function handler (event) {
event.ports[0].postMessage(data);
});
注意 :开发者可以理解为 Service Worker 独立于页面并能与之通信,但无法直接访问页面。
4.Service Worker 常见注意事项
停止 Service Worker
Service Worker 可以随时停止,浏览器不希望将资源浪费在没有执行任何操作的 Service Worker 上。停止与终止并不相同,Service Worker 仍保持安装和激活状态,其直接进入休眠状态了。下次需要请求时,浏览器会将其唤醒。
event.waitUntil
由于 Service Worker 随时可能会休眠,因此需要通过某种方式让浏览器知道其何时正在执行重要的操作,比如: event.waitUntil() 。此方法会延长其生命周期,使其不会停止并进入其生命周期的下一阶段,直到准备就绪,从而有时间设置缓存、从网络中提取资源等。
以下示例告知浏览器,在缓存创建完毕并填充图片之前, Service Worker 并未完成安装:
self.addEventListener("install", (event) => {
const preCache = async () => {
const cache = await caches.open("static-v1");
return cache.addAll(["/", "/about/", "/static/styles.css"]);
};
event.waitUntil(preCache());
});
这里重点讨论两个方法:
- caches.open:返回一个 Promise,解析为与 cacheName 匹配的 Cache 对象
- addAll() 方法接受一个 URL 数组,检索并将生成的 response 对象添加到给定的缓存中。在检索期间创建的 request 对象成为存储的 response 操作的 key。
全局状态
当 start/stop 时,Service Worker 全局作用域会重置。因此,不要在 Service Worker 中使用全局状态,否则下次其被唤醒可能会出现与预期不同的状态。
// 这里使用了全局变量 favoriteNumber,每次都会重新生成
const favoriteNumber = Math.random();
let hasHandledARequest = false;
self.addEventListener("fetch", event => {
console.log(favoriteNumber);
console.log(hasHandledARequest);
hasHandledARequest = true;
});
在每个 fetch 中,Service Worker 都将打印一个数字,假设为 0.13981866382421893,hasHandledARequest 变量也会更改为 true。此时,Service Worker 会暂时处于空闲状态,因此浏览器会停止它。下次有请求时,浏览器会再次唤起 Service Worker,同时执行脚本,此时,hasHandledARequest 已重置为 false,而 favoriteNumber 则完全不同,即 0.5907281835659033。
因此,开发者不要依赖于 Service Worker 中存储的状态。此外,创建 Message Channel 等内容的实例也可能会导致 bug,即每次 Service Worker stops/start 时都将获得一个全新的实例。
Service Worker 连接在一起,但相互独立
网页一次只能由一个 Service Worker 控制,但可以同时安装两个 Service Worker。
当对 Service Worker 代码进行更改并刷新页面时,实际上并没有编辑 Service Worker,因为 Service Worker 是不可变的。新的 Service Worker 会安装,但不会激活,其必须等待当前 Service Worker 终止(当用户离开网站时)。
if ("serviceWorker" in navigator) {
navigator.serviceWorker
.register("/sw.js", { scope: "/"})
.then((registration) => {
// registration worked
console.log("Registration succeeded.");
registration.unregister().then((boolean) => {
// if boolean = true, unregister is successful
});
})
.catch((error) => {
// registration failed
console.error(`Registration failed with ${error}`);
});
}
与其他 Service Worker 的缓存相混淆
在安装过程中,SW2 可以进行一些设置 ,通常是创建和填充缓存。
但请注意,新的 Service Worker 可以访问当前 Service Worker 可以访问的所有内容。如果不小心,新的等待 Service Worker 可能会把当前 Service Worker 弄得一团糟,比如:
- SW2 可以删除 SW1 正在使用的缓存
- SW2 可以修改 SW1 正在使用的缓存的内容,导致 SW1 提供页面不需要的资源进行响应
跳过等待
Service Worker 还可以使用存在风险的 skipWaiting() 方法在完成安装后立即控制页面。除非有意尝试替换有问题的 Service Worker,否则通常不建议采用这种做法,新 Service Worker 可能正在使用当前页面不需要的更新资源,从而导致错误和 bug。
self.addEventListener("install", (event) => {
// 可以安全地忽略 skipWaiting() 返回的 Promise
self.skipWaiting();
// 执行所需的任何其他操作
// 要安装的 Service Worker,可能位于内部 event.waitUntil();
});
虽然 self.skipWaiting() 可以在 Service Worker 执行期间的任何时刻调用,但只有在新安装的 Service Worker 可能在 waiting 状态时才会起作用。 因此,通常从 InstallEvent 处理程序内部调用 self.skipWaiting()。
开始清理
防止 Service Worker 相互破坏的方法是确保其使用不同的缓存,最简单的方法是对所使用的缓存名称进行版本控制。
// 使用版本管理
const version = 1;
const assetCacheName = `assets-${version}`;
self.addEventListener("install", event => {
caches.open(assetCacheName).then(cache => {
// 自信地使用自己的缓存进行操作
});
});
部署新的 Service Worker 时,会自增 version,使其使用与上一个 Service Worker 完全独立的缓存来执行所需的操作。
结束清理
一旦 Service Worker 达到 activated 状态就会知道其已被接管,因此上一个 Service Worker 就是多余的。此时,务必在旧的 Service Worker 之后进行清理,这样不仅可以遵循用户缓存存储空间限制,还可以防止出现意外的错误。
cache.match(request, { options}).then(function (response) {
// 注意: Cache.match() 和 Cache.matchAll() 基本一样,但前者只解析为 response[0] 而不是所有 response
});
caches.match() 是一种常用的快捷方式,用于从存在匹配项的任何缓存中检索项,其会按照创建顺序遍历缓存。因此,假设在两个不同的缓存(assets-1 和 assets-2)中有脚本文件 app.js 的两个版本,网页正在等待存储在 assets-2 中的新脚本。但如果未删除旧缓存,caches.match('app.js') 将返回 assets-1 中的旧缓存,且很有可能会破坏网站。
因此,可以通过下面的方法删除 Service Worker 中不需要的任何缓存:
const version = 2;
const assetCacheName = `assets-${version}`;
self.addEventListener("activate", event => {
event.waitUntil(
// 删除不需要的缓存
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
if (cacheName !== assetCacheName){
return caches.delete(cacheName);
}
});
);
});
);
});
- caches.keys:返回一个 Promise ,该 Promise 将解析为一个 Cache 键数组
- caches.delete(key):查询 request 为 key 的 Cache 条目,如果找到则删除该条目并返回 resolve 为 true 的 Promise 。如果没有找到则返回 resolve 为 false 的 Promise 。
5.本文总结
本文主要和大家介绍 Service worker ,其本质上充当 Web 应用程序、浏览器与网络(可用时)之间的代理服务器,旨在创建有效的离线体验,还提供入口以推送通知和访问后台同步 API。因为篇幅问题,关于 Service worker 主题只是做了一个简短的介绍,但是文末的参考资料提供了大量优秀文档以供学习,如果有兴趣可以自行阅读。如果大家有什么疑问欢迎在评论区留言。
参考资料
注意:本文部分内容借鉴了Dave Geddes的文章《Service worker mindset》
https://www.bothrs.com/story/heres-why-service-workers-have-superpowers
https://web.dev/articles/service-worker-mindset?hl=zh-cn
https://developer.chrome.com/blog/broadcastchannel?hl=zh-cn
https://developer.mozilla.org/zh-CN/docs/Web/API/MessageChannel
https://web.dev/learn/pwa/service-workers?hl=zh-cn
https://ponyfoo.com/articles/serviceworker-messagechannel-postmessage
https://developer.mozilla.org/zh-CN/docs/Web/API/Cache/match
https://googlechrome.github.io/samples/service-worker/custom-offline-page/index.html
https://www.youtube.com/watch?app=desktop&v=1usuYqZMT7Q
猜你喜欢
- 2024-11-07 PWA 对比原生应用:谁更胜一筹?(pwa应用是什么)
- 2024-11-07 用 Service Worker 实现前端性能优化
- 2024-11-07 我采访了一位 Pornhub 工程师,聊了这些纯纯的话题
- 2024-11-07 使用Service Worker让你的 Web 应用如虎添翼(下)「干货」
- 2024-11-07 使用Service Worker让你的 Web 应用如虎添翼(上)「干货」
- 2024-11-07 如何在 Service Worker 重新启动时重用信息
- 2024-11-07 JS 中 service workers 的简介(javascript worker)
- 2024-11-07 如何使用 Service Worker 操作 DOM
- 2024-11-07 Service worker简介(service engineer)
- 2024-11-07 前端基础:什么是Service Worker? 解释其生命周期及使用场景
你 发表评论:
欢迎- 最近发表
- 标签列表
-
- powershellfor (55)
- messagesource (56)
- aspose.pdf破解版 (56)
- promise.race (63)
- 2019cad序列号和密钥激活码 (62)
- window.performance (66)
- qt删除文件夹 (72)
- mysqlcaching_sha2_password (64)
- ubuntu升级gcc (58)
- nacos启动失败 (64)
- ssh-add (70)
- jwt漏洞 (58)
- macos14下载 (58)
- yarnnode (62)
- abstractqueuedsynchronizer (64)
- source~/.bashrc没有那个文件或目录 (65)
- springboot整合activiti工作流 (70)
- jmeter插件下载 (61)
- 抓包分析 (60)
- idea创建mavenweb项目 (65)
- vue回到顶部 (57)
- qcombobox样式表 (68)
- vue数组concat (56)
- tomcatundertow (58)
- pastemac (61)
本文暂时没有评论,来添加一个吧(●'◡'●)