用fw做网站页面,微商如何做网站引流,地图位置链接怎么做,设计师交流平台有哪些与专用工作者线程和共享工作者线程一样#xff0c;服务工作者线程也能与客户端通过postMessage()交换消息。 实现通信的最简单方式是向活动工作者线程发送一条消息#xff0c;然后使用事件对象发送回应。 发送给服务工作者线程的消息可以在全局作用域处理#xff0c;而发送回…与专用工作者线程和共享工作者线程一样服务工作者线程也能与客户端通过postMessage()交换消息。实现通信的最简单方式是向活动工作者线程发送一条消息然后使用事件对象发送回应。发送给服务工作者线程的消息可以在全局作用域处理而发送回客户端的消息则可以在ServiceWorkerContext对象上处理。注册 Service Worker 后可以通过两种方式向其发送消息。✅ 代码功能解析navigator.serviceWorker.register(/test_worker.js).then((registration) { console.log(Service Worker 注册成功); // 方式一通过 controller 发送消息 if (navigator.serviceWorker.controller) { navigator.serviceWorker.controller.postMessage(你好服务工作者线程1); } // 方式二通过 registration.active 发送消息 if (registration.active) { registration.active.postMessage(你好服务工作者线程2); } });这个示例每次页面重新加载时都会运行。这是因为服务工作者线程会回应每次刷新后客户端脚本发送的消息。在通过新标签页打开这个页面时也是一样。1.navigator.serviceWorker.controller表示当前控制该页面的激活activeService Worker。如果页面刚加载完而 SW 刚注册但尚未接管页面比如首次加载controller可能为null。因此通常用于页面已被 SW 控制后的通信。2.registration.active是注册对象中当前处于active 状态的 Service Worker 实例。即使页面尚未被该 SW 控制例如刚刚 install/activate 完成只要它处于 active 状态就可以通过它发消息。更适合在注册成功后立即通信的场景。⚠️ 注意如果 SW 刚注册、尚未 activateregistration.active也可能为null。此时你可能需要监听statechange事件等待其变为activated。 在 Service Worker 中接收消息你需要在/test_worker.js中监听message事件self.onmessage ({data,source}) { console.log(Worker 收到消息:, data); //self.postMessage is not a function //self.postMessage(Worker 回复: 已收到 ${event.data}); source.postMessage(Worker 回复: 已收到 ${data}); };注意event.source只有在消息来自页面client时才存在且需确保客户端支持postMessage回复。 建议更健壮的消息发送方式navigator.serviceWorker.ready是处理这种情况最推荐的方式它会返回一个 Promise在所有服务工作者线程都激活后解析。navigator.serviceWorker.register(/test_worker.js) .then(() { // 等待所有服务工作者线程准备就绪 return navigator.serviceWorker.ready; }) .then(registration { // 此时应该至少有一个激活的 SW if (registration.active) { registration.active.postMessage(你好服务工作者线程); } else { console.warn(没有激活的服务工作者线程); } }) .catch(console.error);✅ 总结方式适用场景注意事项navigator.serviceWorker.controller.postMessage()页面已被 SW 控制后通信首次加载时可能为nullregistration.active.postMessage()注册后立即尝试通信若 SW 尚未 activate也为null建议根据实际生命周期选择合适方式或结合statechange事件确保 SW 已就绪再通信。如需进一步实现双向通信、缓存管理或推送通知也可以继续扩展 Service Worker 功能。registration.installing?.addEventListener中的?.是可选链操作符Optional Chaining是现代 JavaScriptES2020 引入中的合法语法。✅可选链操作符作用解释registration.installing?.addEventListener(...)等价于if (registration.installing ! null) { registration.installing.addEventListener(...); }也就是说如果registration.installing是null或undefined那么整个表达式会安全地短路不会抛出错误。如果它存在是一个 ServiceWorker 实例就正常调用.addEventListener()。 为什么这里需要可选链在 Service Worker 的注册对象ServiceWorkerRegistration中有三个属性表示不同状态的 workerinstalling正在安装的 SW可能为nullwaiting已安装但等待激活的 SW可能为nullactive当前激活的 SW可能为null在刚注册完 SW 时可能还没有installing实例比如注册的是已存在的 SW或者安装过程已经完成installing已变为null。因此直接访问registration.installing.addEventListener可能会报错❌Cannot read property addEventListener of null使用?.就可以避免这种运行时错误。 补充说明可选链不仅用于方法调用还可用于属性访问、数组索引等obj?.prop obj?.[expr] arr?.[0] func?.()在现代前端开发中尤其是处理异步状态、API 响应、浏览器 API 等不确定结构时可选链是非常常用且推荐的安全写法。关联阅读推荐JavaScript 可选链操作符?.详解以下示例会导致控制台消息刷屏陷入消息循环。//main.js if (serviceWorker in navigator) { navigator.serviceWorker .register(/test_worker.js) .then((registration) { console.log(Service Worker 注册成功); // 通知 Service Worker 有客户端连接 if (navigator.serviceWorker.controller) { navigator.serviceWorker.controller.postMessage({ type: CLIENT_CONNECTED, url: window.location.href, }); } }) .catch((error) { console.error(Service Worker 注册失败:, error); }); } navigator.serviceWorker.onmessage ({ data, source }) { console.log(客户端收到消息:, data); source.postMessage(客户端回复消息); }; //test_worker.js self.onmessage ({ data, source }) { console.log(Worker 收到消息:, data); if (source source.postMessage) { source.postMessage(Worker 回复: 已收到 ${data}); } else { console.warn(源对象不存在或没有 postMessage 方法); } }; // 安装事件 - 立即跳过等待阶段 self.addEventListener(install, (event) { console.log(Service Worker 正在安装...); event.waitUntil(self.skipWaiting()); // 立即激活 }); // 激活事件 - 避免在激活时立即向客户端发送消息防止循环 //控制台为什么出现消息刷屏程序是否进入了死循环 //在 Service Worker 中发送消息给客户端客户端又可能触发新的请求从而再次激活 Service Worker self.onactivate () { //获取所有已打开的窗口 //includeUncontrolled 表示包括那些没有被该 service worker 控制的窗口 self.clients.matchAll({ includeUncontrolled: true }).then((clients) { console.log(clients); //TypeError: Cannot read properties of undefined (reading postMessage) //在 Service Worker 中clients[0] 可能是 undefined。这通常是因为 clients.matchAll() 返回的客户端数组可能为空或者第一个客户端对象没有 postMessage 方法。 //clients[0].postMessage(test worker); if (clients clients.length 0) { // 遍历所有客户端找到可以发送消息的客户端 for (let i 0; i clients.length; i) { const client clients[i]; if (client client.postMessage) { client.postMessage(test worker 率先发送消息); break; // 发送成功后退出循环 } } } else { console.warn(没有找到任何客户端); } }); };原始代码的问题self.onactivate () { // 这里的问题 // 1. 没有调用 event.waitUntil() // 2. 可能在 Service Worker 激活期间频繁触发 self.clients.matchAll({ includeUncontrolled: true }).then((clients) { // 向客户端发送消息 client.postMessage(test worker 率先发送消息); }); };真正的问题是缺少event.waitUntil()// 正确做法 self.onactivate (event) { event.waitUntil( // 异步操作 ); };消息循环的可能性Service Worker 激活 → 发送消息客户端收到消息 → 可能触发页面刷新/重载页面重载 → Service Worker 重新激活 → 再次发送消息实际上postMessage()是同步方法它立即返回undefined。应该用try-catch不能用.catch()try { client.postMessage(data); // 发送成功但对方是否收到不确定 } catch (error) { console.error(发送失败:, error); }在activate事件中使用event.waitUntil()来确保 Service Worker 不会过早认为激活已完成。但其中存在一个关键问题你无法在activate事件处理器中直接访问client对象除非你主动获取客户端clients。// 正确即使只有同步操作也使用 waitUntil self.addEventListener(activate, (event) { event.waitUntil( // 即使内部是同步操作也返回 Promise Promise.resolve().then(() { console.log(执行同步操作); // 同步的 postMessage client.postMessage(data);// ← 这里会报错 }) ); });错误原因client未定义在activate事件上下文中没有名为client的变量。即使你想向控制的页面发送消息也必须先通过self.clients.matchAll()或self.clients.claim()获取客户端列表。“即使只有同步操作也使用waitUntil”这是正确的理念但实现方式需注意event.waitUntil()必须接收一个 Promise。如果你只有同步代码可以用Promise.resolve().then(() { /* 同步逻辑 */ })或更简洁地用void表达式包装成 Promiseevent.waitUntil( (async () { // 同步或异步操作都可以 console.log(同步操作); })() );或者仅同步且无副作用时event.waitUntil(Promise.resolve()); // 但这样就不能在 waitUntil 内部做事情了所以只要你在waitUntil中要执行任何逻辑包括发消息就必须把它放进 Promise 链或 async 函数中。总结问题说明❌client未定义activate事件中没有隐式的client必须通过self.clients获取✅ 使用waitUntil是对的确保激活生命周期等待你的操作完成✅ 必须传 Promise 给waitUntil即使内部是同步操作也要包装成 Promise✅ 推荐用async/await更清晰避免回调嵌套最佳实践建议这样既安全又符合 Service Worker 生命周期规范。立即生效无需页面刷新可以向页面发送更新通知self.addEventListener(activate, event { event.waitUntil( (async () { await self.clients.claim(); // 立即控制所有匹配的页面 const clients await self.clients.matchAll({ type: window }); clients.forEach(c c.postMessage({ type: ACTIVATED })); })() ); });典型使用场景// 完整示例激活时更新缓存并通知页面 self.addEventListener(activate, event { event.waitUntil( (async () { // 1. 清理旧缓存 const cacheKeys await caches.keys(); await Promise.all( cacheKeys.map(key { if (key ! CURRENT_CACHE_NAME) { return caches.delete(key); } }) ); // 2. 立即控制所有页面 await self.clients.claim(); // 3. 通知所有页面 const clients await self.clients.matchAll(); clients.forEach(client { client.postMessage({ type: SW_ACTIVATED, payload: { version: 2.0.0 } }); }); })() ); });谨慎使用claim()可能导致 Service Worker 过早接管页面如果 Service Worker 还在安装中可能会出现问题建议只在确保 SW 已完全准备好时使用兼容性确保所有目标浏览器都支持clients.claim()替代方案// 如果不需立即控制可以更简单 self.addEventListener(activate, event { event.waitUntil(self.clients.claim()); // 或者只清理缓存 event.waitUntil(cleanupOldCaches()); });这个模式在 PWA 开发中很常见特别是在需要立即更新缓存或通知用户有新版本时特别有用。核心要点总结✅postMessage()是同步方法返回undefined✅ 即使只有同步操作也应使用event.waitUntil()✅activate事件每个 Service Worker 版本只触发一次✅ 刷屏是由message事件循环引起的不是activate重复触发✅ 防止循环的关键客户端不要自动回复消息消息循环风险Service Worker 激活时发送消息 → 客户端收到消息可能触发刷新或重新加载 → 再次激活 Service Worker优化后的代码main.js (客户端)if (serviceWorker in navigator) { navigator.serviceWorker .register(/test_worker.js) .then((registration) { console.log(Service Worker 注册成功); // 监听 Service Worker 状态变化 if (registration.installing) { registration.installing.addEventListener(statechange, () { if (registration.active) { // 只在激活时发送一次连接消息 registration.active.postMessage({ type: CLIENT_CONNECTED, url: window.location.href, timestamp: Date.now() }); } }); } }) .catch((error) { console.error(Service Worker 注册失败:, error); }); // 消息处理 - 添加防循环逻辑 navigator.serviceWorker.addEventListener(message, ({ data, source }) { console.log(客户端收到消息类型:, data.type, 内容:, data.message); // 关键不回复某些类型的消息 if (data.type INITIAL_GREETING || data.type WELCOME) { console.log(收到问候消息不回复以避免循环); return; // 不回复这些消息 } // 只回复特定类型的消息 if (data.type REQUEST_REPLY) { console.log(收到请求回复的消息进行回复); if (source source.postMessage) { source.postMessage({ type: CLIENT_REPLY, message: 这是客户端回复, timestamp: Date.now() }); } } }); } // 可选添加页面卸载时的清理 window.addEventListener(beforeunload, () { if (navigator.serviceWorker.controller) { navigator.serviceWorker.controller.postMessage({ type: CLIENT_DISCONNECTED }); } });main.js- 防止循环的关键if (serviceWorker in navigator) { navigator.serviceWorker .register(/test_worker.js) .then((registration) { console.log(Service Worker 注册成功); }) .catch((error) { console.error(Service Worker 注册失败:, error); }); // 关键不要自动回复所有消息 navigator.serviceWorker.addEventListener(message, ({ data }) { console.log(客户端收到消息:, data); // 方案1完全不回复推荐 // 什么都不做 // 方案2只回复特定类型 // if (data.type REQUEST_REPLY) { // // 根据需要回复 // } }); // 如果需要可以主动发送连接消息 setTimeout(() { if (navigator.serviceWorker.controller) { navigator.serviceWorker.controller.postMessage({ type: CLIENT_CONNECTED }); } }, 1000); }test_worker.js (Service Worker)// test_worker.js - 修复版 self.addEventListener(activate, (event) { console.log(Service Worker 激活); event.waitUntil( self.clients.claim().then(() { return self.clients.matchAll({ includeUncontrolled: false, type: window }); }).then(clients { // 使用 try-catch 而不是 .catch() clients.forEach(client { if (!processedClients.has(client.id)) { processedClients.add(client.id); try { // postMessage 是同步方法不返回 Promise client.postMessage({ type: INITIAL_GREETING, message: Service Worker 已激活, requireReply: false, timestamp: Date.now() }); } catch (err) { console.warn(发送初始消息失败:, err); } } }); }).catch(error { // 这是处理前面 Promise 链的错误 console.error(激活过程中出错:, error); }) ); }); // postMessage() 是同步方法不返回 Promise所以不能使用 .catch() // 使用 try-catch 来捕获可能的错误 // 考虑是否真的需要发送激活消息 - 很多时候不需要 // 如果必须发送确保客户端不会自动回复避免消息循环 // 最简单的解决方案不在 activate 事件中发送消息这样可以避免所有相关问题。test_worker.js完整示例// 存储激活状态 let isActivated false; // 安装事件 - 只触发一次 self.addEventListener(install, (event) { console.log(Service Worker 安装); // 强制立即激活 event.waitUntil(self.skipWaiting()); }); // 激活事件 - 只触发一次 self.addEventListener(activate, (event) { // 如果已经激活过直接返回 if (isActivated) { console.log(Service Worker 已经激活过跳过); return; } console.log(Service Worker 激活第一次); isActivated true; // 正确使用 event.waitUntil event.waitUntil( // 异步操作序列 (async () { try { // 1. 立即接管所有客户端 await self.clients.claim(); console.log(已接管客户端); // 2. 获取所有客户端 const clients await self.clients.matchAll({ includeUncontrolled: false, type: window }); console.log(找到 ${clients.length} 个客户端); // 3. 同步发送消息不需要 await clients.forEach(client { if (client client.postMessage) { try { // postMessage 是同步的 client.postMessage({ type: SERVICE_WORKER_ACTIVATED, message: Service Worker 已激活, timestamp: Date.now() }); console.log(消息发送成功); } catch (sendError) { console.warn(发送消息失败:, sendError); // 发送失败不影响激活过程 } } }); console.log(激活过程完成); return true; // 表示激活成功 } catch (error) { console.error(激活过程出错:, error); // 即使出错也要让激活完成 return false; } })() ); }); // 消息事件 - 可多次触发 self.addEventListener(message, (event) { console.log(收到消息:, event.data); // 防止消息循环的关键不要无条件回复 if (event.data.type REQUEST_REPLY) { // 只有明确请求时才回复 if (event.source event.source.postMessage) { try { event.source.postMessage({ type: REPLY, message: 收到你的请求 }); } catch (error) { console.warn(回复消息失败:, error); } } } // 其他类型的消息不回复 });关键改进点优化语法将self.onactivate改为self.addEventListener(activate, ...)添加消息类型区分不同类型的消息避免循环响应添加延迟在激活后延迟发送消息确保客户端准备就绪添加状态检查防止重复发送初始消息错误处理为所有异步操作添加错误处理条件过滤只向可见的窗口客户端发送消息使用clients.claim()确保 Service Worker 立即控制所有客户端版本控制在 Service Worker 注册 URL 中添加版本号防止缓存问题防止死循环的建议避免在 activate 事件中无条件发送消息使用消息类型标识客户端可以忽略某些类型的消息考虑使用超时机制限制消息发送频率在生产环境中考虑只在特定条件下发送初始消息如首次安装时这样优化后代码更加健壮能够有效避免消息刷屏和死循环问题。总结self.onactivate 和 self.addEventListener(activate, ...)两种语法都有效但addEventListener是更灵活、更推荐的方式原始代码的主要问题是缺少event.waitUntil()包装异步操作没有防止消息循环的机制没有错误处理建议的实践使用addEventListener以便添加多个监听器总是使用event.waitUntil()包装 activate 事件中的异步操作添加防止循环的逻辑如消息类型检查、发送次数限制Service Worker 事件触发次数一次性事件每个 Service Worker 版本只触发一次install事件- 只在 Service Worker新安装或更新时触发一次activate事件- 只在 Service Worker首次激活或更新后激活时触发一次可重复触发的事件fetch事件- 每次有网络请求时都可能触发message事件- 每次收到消息时触发push事件- 每次收到推送通知时触发sync事件- 后台同步时触发为什么示例代码会刷屏虽然activate事件只触发一次但消息循环会导致刷屏问题场景1. Service Worker 激活 (activate 触发一次) → 发送消息 2. 客户端收到消息 → 自动回复 3. Service Worker 收到回复 (message 事件触发) → 再次回复 4. 客户端收到回复 → 再次自动回复 5. 无限循环... (message 事件不断触发)activate只触发一次但message事件会不断触发形成循环。生命周期图示第一次访问页面: 页面加载 → Service Worker 注册 → install (一次) → activate (一次) 刷新页面: 页面加载 → Service Worker 已存在 → 直接控制页面 ↓ 可能触发 fetch/message 事件多次 更新 Service Worker: install (新版本一次) → 等待 → activate (新版本一次)正确的实践方案A不在 activate 中发送消息self.addEventListener(activate, (event) { console.log(activate 触发一次); event.waitUntil( self.clients.claim().then(() { console.log(激活完成不发送消息); // 不发送任何消息 }) ); }); // 让客户端主动连接 self.addEventListener(message, (event) { if (event.data.type CLIENT_CONNECTED) { // 只回复一次欢迎消息 event.source.postMessage({ type: WELCOME, message: 连接成功 }); } });方案B使用一次性标志let hasSentInitialMessage false; self.addEventListener(activate, (event) { console.log(activate 触发一次); event.waitUntil( self.clients.claim().then(() { if (!hasSentInitialMessage) { hasSentInitialMessage true; return self.clients.matchAll().then(clients { // 只发送一次 clients.forEach(client { client.postMessage({ type: INITIAL_GREETING }); }); }); } }) ); });方案C让客户端控制通信// main.js navigator.serviceWorker.addEventListener(message, ({ data }) { console.log(收到:, data); // 不自动回复 }); // 页面加载后主动连接 if (navigator.serviceWorker.controller) { navigator.serviceWorker.controller.postMessage({ type: CLIENT_READY }); } // test_worker.js self.addEventListener(message, (event) { if (event.data.type CLIENT_READY) { // 只回复一次 event.source.postMessage({ type: SERVICE_WORKER_READY }); } });总结activate事件确实每个 Service Worker 版本只触发一次消息循环是由message事件反复触发引起的最佳实践避免在 Service Worker 激活时自动发送消息推荐方案让客户端主动发起连接Service Worker 只做响应所以刷屏不是activate事件重复触发而是message事件的循环导致的。补充为什么会没有找到任何客户端在 Service Worker 中没有找到任何客户端的原因通常有以下几种Service Worker 注册时还没有任何客户端页面当 Service Worker 刚注册时可能还没有任何页面客户端与之关联。Service Worker 的作用域不正确如果 Service Worker 的作用域不包含当前页面那么它无法控制该页面。Service Worker 还没有完全激活onactivate 事件在 Service Worker 激活时触发但此时可能还没有任何客户端连接。页面直接打开而非通过服务器访问Service Worker 需要在 HTTPS 或 localhost 环境下才能工作。