You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让Service Worker仅在用户首次访问时注册?

问题:如何让Service Worker注册代码仅在用户首次访问时执行?

您好,我使用Express框架的EJS模板生成HTML页面,将Service Worker注册代码写在了网站所有页面共用的模板中,导致每个页面都包含这段注册代码,用户每次访问时都会执行该代码,我认为这并不合理。请问如何让这段代码仅在用户首次访问时运行?

以下是我的代码:

<script>
if ('serviceWorker' in navigator) {
  window.addEventListener('load', function() {
    var hashes = {};
    ["appCss", "appJs"].map((val,idx) => {
      let prop;
      if(document.getElementById(val)) {
        prop = val.toLowerCase().indexOf("css") == -1 ? "src" : "href";
        hashes[val] = document.getElementById(val)[prop];
      }
    });
    hashes = JSON.stringify(hashes);
    navigator.serviceWorker
      .register(`/service-worker.js?hash=${encodeURIComponent(hashes)}`)
      .then(function(registration) {
        console.log('ServiceWorker registration successful with scope: ', registration.scope);
      }, function(err) {
        console.log('ServiceWorker registration failed:', err);
      });
  });
}
</script>

解决方案

先补充个小知识点:Service Worker的注册本身是幂等操作——就算多次调用navigator.serviceWorker.register(),浏览器只会在没有匹配的已注册Service Worker时,才会真正执行注册流程。不过如果你想完全避免重复执行这段注册代码的逻辑,可以试试下面几种方法:

方法1:用本地存储标记已注册状态

在注册成功后,通过localStorage存储一个标记,下次页面加载时先检查这个标记,只有不存在时才执行注册代码:

修改后的代码如下:

<script>
if ('serviceWorker' in navigator) {
  window.addEventListener('load', function() {
    // 检查本地存储是否有已注册的标记
    const hasRegisteredSW = localStorage.getItem('swRegistered');
    if (!hasRegisteredSW) {
      var hashes = {};
      ["appCss", "appJs"].map((val,idx) => {
        let prop;
        if(document.getElementById(val)) {
          prop = val.toLowerCase().indexOf("css") == -1 ? "src" : "href";
          hashes[val] = document.getElementById(val)[prop];
        }
      });
      hashes = JSON.stringify(hashes);
      navigator.serviceWorker
        .register(`/service-worker.js?hash=${encodeURIComponent(hashes)}`)
        .then(function(registration) {
          console.log('ServiceWorker registration successful with scope: ', registration.scope);
          // 注册成功后,在本地存储写入标记
          localStorage.setItem('swRegistered', 'true');
        }, function(err) {
          console.log('ServiceWorker registration failed:', err);
        });
    }
  });
}
</script>

方法2:直接检查已有的Service Worker注册状态

通过navigator.serviceWorker.getRegistrations()获取所有已注册的Service Worker,判断是否已有匹配当前作用域的注册,避免重复执行代码:

<script>
if ('serviceWorker' in navigator) {
  window.addEventListener('load', async function() {
    // 获取所有已注册的Service Worker实例
    const registrations = await navigator.serviceWorker.getRegistrations();
    // 检查是否有匹配当前网站根路径作用域的注册
    const hasExistingSW = registrations.some(reg => reg.scope === window.location.origin + '/');
    
    if (!hasExistingSW) {
      var hashes = {};
      ["appCss", "appJs"].map((val,idx) => {
        let prop;
        if(document.getElementById(val)) {
          prop = val.toLowerCase().indexOf("css") == -1 ? "src" : "href";
          hashes[val] = document.getElementById(val)[prop];
        }
      });
      hashes = JSON.stringify(hashes);
      navigator.serviceWorker
        .register(`/service-worker.js?hash=${encodeURIComponent(hashes)}`)
        .then(function(registration) {
          console.log('ServiceWorker registration successful with scope: ', registration.scope);
        }, function(err) {
          console.log('ServiceWorker registration failed:', err);
        });
    }
  });
}
</script>

方法3:服务器端控制(适配EJS模板场景)

如果想从模板层面就避免输出这段代码,可以在Express路由中给首次访问的用户设置一个Cookie,后续请求时检查Cookie,只有没有Cookie时才渲染注册脚本:

步骤1:在Express路由中添加Cookie逻辑

app.get('*', (req, res, next) => {
  // 检查是否存在标记已注册的Cookie
  if (!req.cookies.sw_registered) {
    // 设置有效期为1年的Cookie,标记用户已看到注册代码
    res.cookie('sw_registered', 'true', { maxAge: 365 * 24 * 60 * 60 * 1000 });
    // 给EJS模板传递渲染标记
    res.locals.showSWRegistration = true;
  } else {
    res.locals.showSWRegistration = false;
  }
  next();
});

步骤2:在EJS共用模板中条件渲染脚本

<% if (showSWRegistration) { %>
<script>
if ('serviceWorker' in navigator) {
  window.addEventListener('load', function() {
    var hashes = {};
    ["appCss", "appJs"].map((val,idx) => {
      let prop;
      if(document.getElementById(val)) {
        prop = val.toLowerCase().indexOf("css") == -1 ? "src" : "href";
        hashes[val] = document.getElementById(val)[prop];
      }
    });
    hashes = JSON.stringify(hashes);
    navigator.serviceWorker
      .register(`/service-worker.js?hash=${encodeURIComponent(hashes)}`)
      .then(function(registration) {
        console.log('ServiceWorker registration successful with scope: ', registration.scope);
      }, function(err) {
        console.log('ServiceWorker registration failed:', err);
      });
  });
}
</script>
<% } %>

补充说明

  • 方法1和方法2是客户端层面的控制,优点是不需要修改服务器代码,适合纯前端或快速迭代场景;
  • 方法3是服务器模板层面的控制,直接避免了不必要的脚本输出,性能更优;
  • 即使你不做任何改动,浏览器本身也会自动忽略重复的注册请求,但减少不必要的代码执行和网络请求始终是更好的实践。

内容的提问来源于stack exchange,提问作者Vishak Uday

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 09:03:36