Skip to content

本节内容是对于MDN上相关章节的总结记录。

重点归纳总结 Service Worker 生态中所有相关的 API。

1.Main Thread ​

在 Main Thread 中涉及到的顶级 API 是 ServiceWorkerContainer。

ServiceWorkerContainer 继承自 EventTarget。

1-1.navigator.serviceWorker ​

我们通常不会直接使用全局构造函数 ServiceWorkerContainer,而是使用一个已有实例 navigator.serviceWorker。

js
console.log(navigator.serviceWorker instanceof ServiceWorkerContainer)

1-2.实例属性 ​

1-2-1.controller ​

该属性通常可用于判断 serviceWorker 是否已激活。

如果 navigator.serviceWorker 的 state 是 activating 或 activated,那么 navigator.serviceWorker.controller 属性会返回一个 serviceWorker 对象。

如果 serviceWorker 未运行或者浏览器强制刷新(shift + reload),那么此 controller 会返回 null。

js
console.log(navigator.serviceWorker.controller)

1-2-2.ready ​

该属性提供了 serviceWorker 激活后的后置方法。

navigator.serviceWorker.ready 会返回一个 promise 对象。

如果 promise 是 resolved 则会返回 registration 实例,否则 rejected 会返回 error。

MDN上说该属性只会 resolved。

js
navigator.serviceWorker.ready.then(registration => {
  console.log(`A service worker is active: ${registration.active}`)
})

1-3.实例方法 ​

1-3-1.getRegistration() ​

navigator.serviceWorker.getRegistration() 方法用来获取指定 clientURL 下的一个注册服务。

js
navigator.serviceWorker.getRegistration().then(registration => {
  console.log(`getRegistration: ${registration}`)
})

其中,clientURL 如果没有指定,则默认是当前 Main Thread JavaScript 文件执行目录。

js
// 获取默认目录下
navigator.serviceWorker.getRegistration()

// 获取/app下
navigator.serviceWorker.getRegistration('/app')

1-3-2.getRegistrations() ​

navigator.serviceWorker.getRegistrations() 方法用来获取指定 clientURL 下的所有注册服务。

js
navigator.serviceWorker.getRegistrations().then(registrations => {
  console.log(`getRegistrations: ${registrations}`)
})

registrations 是数组形式,集合了所有注册服务。

js
// 获取默认目录下
navigator.serviceWorker.getRegistrations()

// 获取/app下
navigator.serviceWorker.getRegistrations('/app')

1-3-3.register() ​

navigator.serviceWorker.register() 方法用来手动注册服务。

基础语法:

js
register(scriptURL)

register(scriptURL, options)
  1. scriptURL 用来指定 serviceWorker 脚本地址。

  2. options 包含以下可选配置:

    • scope 作用域,默认是 ./
    • type 服务类型,classic 或 module
    • updateViaCache 更新机制 TODO: 确认下这里的具体机制作用
js
navigator.serviceWorker.register('./sw.js').then(registration => {
  console.log(`register: ${registration}`)
}).catch(err => {
  console.error(`register fail: ${err}`)
})

假设上述代码运行在 example.com/index.html,则 example.com/sw.js 注册作用域会在 example.com/ 目录下生效。

js
navigator.serviceWorker.register('./sw.js', {
  scope: '/app/'
}).then(registration => {
  console.log(`register: ${registration}`)
}).catch(err => {
  console.error(`register fail: ${err}`)
})

假设上述代码运行在 example.com/index.html,则 example.com/sw.js 注册作用域会在 example.com/app/ 目录下生效。

1-3-4.startMessages() ​

默认情况下,如果 ServiceWorker 中的 postMessage 向 Main Thread 传递消息时,Main Thread 会在 DOMContentLoaded 加载完成后,逐个处理 message。

可以利用 startMessages() 方法提前在 DOMContentLoaded 之前执行处理 message。

js
navigator.serviceWorker.startMessages()

1-4.事件监听 ​

1-4-1.controllerchange ​

在 Main Thread 中监听 Main Thread 的 navigator.serviceWorker 的 controller 发生改变。

js
navigator.serviceWorker.addEventListener('controllerchange', e => {
  console.log('controllerchange', e)
})

TIP

笔者测试了下,在 navigator.serviceWorker.controller 发生改变时,该事件始终没有被触发。

1-4-2.message ​

在 Main Thread 中监听 Worker Thread 中利用 postMessage 传递的信息。

js
navigator.serviceWorker.addEventListener('message', e => {
  console.log(`message: `, e.data)
})

2.ServiceWorkerRegistration ​

当 navigator.serviceWorker 执行 register() 方法注册之后,会返回 ServiceWorkerRegistration 的 registration 实例对象。

ServiceWorkerRegistration 同样继承自 EventTarget。

2-1.实例属性 ​

2-1-1.active ​

该属性会返回 state 为 activating 或 activated 的 ServiceWorker 。

默认为 null。

2-1-2.installing ​

该属性会返回 state 为 installing 的 ServiceWorker 。

默认为 null。

2-1-3.waiting ​

该属性会返回 state 为 installed 的 ServiceWorker 。

默认为 null。

2-1-4.navigationPreload ​

该属性会返回NavigationPreloadManager的一个实例。

通常可用于控制资源的预加载。

2-1-5.pushManager ​

该属性会返回PushManager的一个实例。

2-1-6.scope ​

该属性会返回 ServiceWorker 的注册作用域。

譬如在 http://127.0.0.1:8086/web-worker-service/index.html 下执行:

js
navigator.serviceWorker.register('./sw.js').then(registration => {
  console.log(`register: `, registration.scope)
}).catch(err => {
  console.error(`register fail: ${err}`)
})

其 registration.scope 会打印 http://127.0.0.1:8086/web-worker-service/。

2-1-7.updateViaCache ​

该属性会返回 ServiceWorker 使用的 HTTP 缓存策略。

  • imports 默认值。只有 importScripts 使用 HTTP 缓存。

  • all 所有资源都使用 HTTP 缓存。

  • none 所有资源都不使用 HTTP 缓存。

TODO: 确认下该属性的实际作用

2-2.实例方法 ​

2-2-1.getNotifications() ​

getNotifications() 方法用于获取当前页面上的所有通知。

js
// sw.js
self.addEventListener('notificationclick', event => {
  event.notification.close(); // 关闭被点击的通知

  // 在这里可以添加处理通知点击的逻辑
  console.log('Notification clicked:', event.notification.data);

  // 例如,打开一个链接
  clients.openWindow('https://example.com');
});

// 在某个地方需要获取当前页面上的通知
function handleNotifications() {
  self.registration.getNotifications().then(notifications => {
    notifications.forEach(notification => {
      console.log('Existing notification:', notification.data);

      // 在这里可以执行其他操作,如修改通知内容等
    });
  });
}

// 调用获取通知的函数
handleNotifications();

2-2-2.showNotification() ​

showNotification() 方法用于展示指定通知。

js
// sw.js
self.addEventListener('push', event => {
  const options = {
    body: event.data.text(), // 推送消息的正文
    icon: 'path/to/icon.png', // 通知图标
    data: {
      // 自定义数据,可用于处理通知点击等事件
      customKey: 'customValue'
    }
  };

  event.waitUntil(
    self.registration.showNotification('Push Notification', options)
  );
});

self.addEventListener('notificationclick', event => {
  event.notification.close(); // 关闭通知

  // 处理通知点击事件,可以执行自定义逻辑
  console.log('Notification clicked:', event.notification.data);

  // 例如,打开一个链接
  clients.openWindow('https://example.com');
})

2-2-3.unregister() ​

unregister() 方法支持手动取消注册服务。

返回值是一个 promise,如果取消成功,则 resolved 值为 true,否则为 false。

js
navigator.serviceWorker.register('./sw.js')
  .then(registration => {
    registration.unregister().then(boolean => {
      console.log(`unregister is ${boolean}`)
    })
  })

2-2-4.update() ​

update() 方法会更新 Service Worker,它会 fetch 定义好的 Script URL,进而比对字节。如果前后两者不一致的话,会安装新的 Service Worker。

2-3.事件 ​

2-3-1.updatefound ​

updatefound 事件会在 Service Worker 发生更新时触发。

也就是说当 ServiceWorkerRegistration.installing 属性取得新的 Service Worker 时触发。

3.Worker Thread ​

Service Worker 的线程作用域是 ServiceWorkerGlobalScope。

ServiceWorkerGlobalScope 继承自 WorkerGlobalScope。

3-1.实例属性 ​

3-1-1.clients ​

self.clients 属性会返回Clients的一个实例。

3-1-2.registration ​

self.registration 属性会返回ServiceWorkerRegistration的一个实例。

3-2.实例方法 ​

3-2-1.skipWaiting() ​

Service Worker 的基础生命周期流程如下:

  1. installing

  2. waiting

  3. active

调用 self.skipWaiting() 方法会使得 Service Worker 跳过 waiting,从而处于 active 激活阶段。

3-3.事件 ​

3-3-1.install ​

install 事件监听,在 Service Worker 首次注册时触发,用于执行一次性的安装任务,例如缓存资源。

当 Service Worker 被安装后,它就会进入等待激活状态,直到没有任何当前使用中的页面使用旧的 Service Worker 为止。

js
// sw.js
self.addEventListener('install', event => {
  event.waitUntil(
    caches.open('my-cache').then(cache => {
      return cache.addAll([
        '/',
        '/index.html',
        '/styles.css',
        '/script.js'
      ]);
    })
  );
});

3-3-2.activate ​

activate 事件监听,当 Service Worker 处于 activating 或 activated 状态时触发。

即 activate 事件在 Service Worker 安装后首次激活以及之后每次切换到新版本时触发。

activate 事件通常用于执行清理旧缓存、处理旧版本的 Service Worker 等任务。

js
// sw.js
self.addEventListener('activate', event => {
  event.waitUntil(
    caches.keys().then(cacheNames => {
      // 清理旧版本缓存
      return Promise.all(
        cacheNames.filter(cacheName => {
          return cacheName.startsWith('my-cache') && cacheName !== 'my-cache';
        }).map(cacheName => {
          return caches.delete(cacheName);
        })
      );
    })
  );
});

3-3-3.fetch ​

fetch 事件监听,当 Main Thread 发生网络请求时触发,不限于 ajax、fetch 或者静态资源请求(html、js、css 等等)。

通过监听 fetch 事件,可以实现自定义的缓存策略、资源代理、或其他网络请求相关的逻辑。

js
// sw.js
self.addEventListener('fetch', event => {
  // 拦截并处理网页发起的所有网络请求
  console.log('Fetching:', event.request.url);

  // 示例:自定义缓存策略
  event.respondWith(customCacheStrategy(event.request));
});

function customCacheStrategy(request) {
  // 在这里可以实现自定义的缓存策略
  // 返回一个 Promise,用于响应网络请求

  return caches.match(request).then(cachedResponse => {
    // 如果缓存中有匹配的响应,则直接返回缓存
    if (cachedResponse) {
      console.log('Cache hit:', request.url);
      return cachedResponse;
    }

    // 否则,发起网络请求并缓存响应
    console.log('Cache miss, fetching from network:', request.url);
    return fetch(request).then(response => {
      // 注意:需要将响应克隆一份,因为 Response 对象是单次使用的
      let responseToCache = response.clone();

      caches.open('my-cache').then(cache => {
        cache.put(request, responseToCache);
      });

      return response;
    });
  });
}

3-3-4.message ​

message 事件监听,用来对信息通信作出响应。

js
// main.js
if (navigator.serviceWorker) {
  navigator.serviceWorker.register('service-worker.js')

  navigator.serviceWorker.addEventListener('message', (event) => {
    // event is a MessageEvent object
    console.log(`The service worker sent me a message: ${event.data}`)
  })

  navigator.serviceWorker.ready.then((registration) => {
    registration.active.postMessage('Hi service worker')
  })
}
js
// sw.js
self.addEventListener('message', (event) => {
  // event is an ExtendableMessageEvent object
  console.log(`The client sent me a message: ${event.data}`)

  event.source.postMessage('Hi client')
});

3-3-5.notificationclick ​

notificationclick 事件监听,用来处理通知横幅点击的效果。

js
self.addEventListener('notificationclick', (event) => {
  console.log('On notification click: ', event.notification.tag)
  event.notification.close()

  // This looks to see if the current is already open and
  // focuses if it is
  event.waitUntil(
    clients
      .matchAll({
        type: 'window'
      })
      .then((clientList) => {
        for (const client of clientList) {
          if (client.url === '/' && 'focus' in client) return client.focus()
        }
        if (clients.openWindow) return clients.openWindow('/')
      })
  )
})

3-3-6.notificationclose ​

notificationclose 事件监听,用来处理通知横幅关闭的效果。

js
self.addEventListener('notificationclose', event => {
  // do something
})

3-3-7.push ​

push 事件监听,用来处理服务端向客户端推送的信息。

js
self.addEventListener(
  'push',
  (event) => {
    let message = event.data.json()
    switch (message.type) {
      case 'init':
        doInit()
        break
      case 'shutdown':
        doShutdown()
        break
    }
  },
  false
)

在服务端,可以使用 node.js 封装好的库web-push来进行消息推送。

3-3-8.pushsubscriptionchange ​

pushsubscriptionchange 事件是由浏览器自动触发的,而不是由开发者手动触发的。

这个事件会在以下情况下被浏览器触发:

  1. 订阅状态变化: 当用户订阅或取消订阅推送通知时,pushsubscriptionchange 事件将被触发。

  2. 推送服务端点变化: 如果推送服务端点(Push Service Endpoint)发生变化,例如由于推送服务商更改了服务端点,pushsubscriptionchange 事件也会被触发。

譬如,以下代码实现了,在订阅过期时,重新发起订阅请求,并将最新的订阅信息同步到服务端:

js
self.addEventListener(
  'pushsubscriptionchange',
  (event) => {
    const conv = (val) =>
      btoa(String.fromCharCode.apply(null, new Uint8Array(val)))
    const getPayload = (subscription) => ({
      endpoint: subscription.endpoint,
      publicKey: conv(subscription.getKey('p256dh')),
      authToken: conv(subscription.getKey('auth'))
    })

    const subscription = self.registration.pushManager
      .subscribe(event.oldSubscription.options)
      .then((subscription) =>
        fetch('register', {
          method: 'post',
          headers: {
            'Content-type': 'application/json'
          },
          body: JSON.stringify({
            old: getPayload(event.oldSubscription),
            new: getPayload(subscription)
          })
        })
      )
    event.waitUntil(subscription)
  },
  false
)

3-3-9.sync ​

Service Worker 本身是支持离线工作的。

而 sync 事件监听,则是提供了在网络连接恢复时,将客户端收集的数据同步到服务端。

js
// main.js
navigator.serviceWorker.ready.then(registration => {
  return registration.sync.register('mySyncEvent')
})

// sw.js
self.addEventListener('sync', (event) => {
  if (event.tag === 'mySyncEvent') {
    event.waitUntil(syncDataWithServer())
  }
})

function syncDataWithServer() {
  // 在这里执行数据同步操作,将离线时收集的数据发送到服务器
  const cachedData = JSON.parse(localStorage.getItem('cachedData')) || []
  return fetch('/sync-data-endpoint', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(cachedData)
  })
    .then(response => response.json())
    .then(data => {
      console.log('Data synced with server:', data)
    })
    .catch(error => {
      console.error('Sync failed:', error)
    })
}

4.CacheStorage ​

CacheStorage 顾名思义,是一个 Cache 仓库。

它用来管理具体的 Cache 对象。可通过全局变量 caches 来访问。

4-1.实例方法 ​

4-1-1.match() ​

match() 方法用来判断 CacheStorage 中是否有匹配的 Cache。

match(request, options) 参数介绍如下:

  • request 一个Request对象或者 URL 字符串。

  • options

    • ignoreSearch
    • ignoreMethod
    • ignoreVary
    • cacheName
js
self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request).then((response) => {
      // caches.match() always resolves
      // but in case of success response will have value
      if (response !== undefined) {
        return response
      } else {
        return fetch(event.request)
          .then((response) => {
            // response may be used only once
            // we need to save clone to put one copy in cache
            // and serve second one
            let responseClone = response.clone()

            caches.open('v1').then((cache) => {
              cache.put(event.request, responseClone)
            })
            return response
          })
          .catch(() => caches.match('/gallery/myLittleVader.jpg'))
      }
    })
  )
})

4-1-2.has() ​

has() 方法用以判断 CacheStorage 中是否包含指定 Cache。

has(cacheName) 参数介绍如下:

  • cacheName 目标 cache 名
js
caches
  .has('v1')
  .then((hasCache) => {
    if (!hasCache) {
      someCacheSetupFunction()
    } else {
      caches.open('v1').then((cache) => cache.addAll(myAssets))
    }
  })
  .catch(() => {
    // Handle exception here.
  })

4-1-3.open() ​

open() 方法用以打开或创建 CacheStorage 中的具体 Cache。

open(cacheName) 参数介绍如下:

  • cacheName 目标 cache 名
js
self.addEventListener('install', (event) => {
  event.waitUntil(
    caches
      .open('v1')
      .then((cache) =>
        cache.addAll([
          '/',
          '/index.html',
          '/style.css',
          '/app.js',
          '/image-list.js',
          '/star-wars-logo.jpg',
          '/gallery/bountyHunters.jpg',
          '/gallery/myLittleVader.jpg',
          '/gallery/snowTroopers.jpg'
        ])
      )
  )
})

4-1-4.delete() ​

delete() 方法用以删除 CacheStorage 中的具体 Cache。

它的基础语法是 delete(cacheName)。

如果在 CacheStorage 中成功删除了指定 Cache,则 promise 的 resolved 结果是 true。

如果在 CacheStorage 中未找到指定的 Cache,则 promise 的 resolved 结果是 false。

js
self.addEventListener('activate', (event) => {
  const cachesToKeep = ['v2']

  event.waitUntil(
    caches.keys().then((keyList) =>
      Promise.all(
        keyList.map((key) => {
          if (!cachesToKeep.includes(key)) {
            return caches.delete(key)
          }
        })
      )
    )
  )
})

4-1-5.keys() ​

keys() 方法用以获取 CacheStorage 中的所有 Cache 键。

js
this.addEventListener('activate', (event) => {
  const cacheAllowlist = ['v2']

  event.waitUntil(
    caches.keys().then((keyList) =>
      Promise.all(
        keyList.map((key) => {
          if (!cacheAllowlist.includes(key)) {
            return caches.delete(key)
          }
        })
      )
    )
  )
})

5.Cache ​

5-1.实例方法 ​

js
(async function() {
  await caches.delete('v1')
  const cache = await caches.open('v1')
  const pathA = await cache.add('/')
  const searchPathA = await cache.add('/?query=1')
  const pathB = await cache.addAll(['/web-worker-service', '/web-worker-service?query=1'])
  const pathC = await cache.put('/?query=2', new Response())
  const match = await cache.match('/', {
    ignoreSearch: true
  })
  const matchAll = await cache.matchAll('/', {
    ignoreSearch: true
  })
  const keys = await cache.keys()
  console.log({
    pathA,
    searchPathA,
    pathB,
    pathC
  })
  console.log('match', match)
  console.log('matchAll', matchAll)
  console.log('keys', keys)
})()

5-1-1.match() ​

match() 方法用来匹配指定 request 对应的第一个 response。

js
cache.match(request, options)

5-1-2.matchAll() ​

matchAll() 方法用来匹配指定 request 对应的所有 response。

js
cache.matchAll(request, options)

5-1-3.add() ​

add() 方法用来向 cache 中添加目标 request 对象或者 URL。

在执行之后,浏览器会自动根据传入的 request 对象或者 URL 进行解析。

然后将与 request 对象或者 URL 的 response 关联。

js
cache.add(request)

5-1-4.addAll() ​

addAll() 方法用来向 cache 中添加一系列 request 对象或者 URL。

addAll() 与 add() 的区别,在于 addAll() 添加的是数组形式:

js
cache.addAll([request, request, ...])

5-1-5.put() ​

前文中我们介绍了 add() 与 addAll() 方法。

如果我们要添加自定义 cache 的话,可以使用 put() 方法。

js
cache.put(request, response)

譬如:

js
cache.put('/', new Response())

5-1-6.delete() ​

delete() 方法相对于 add() 方法,用来删除 cache。

js
cache.delete(request, options)

5-1-7.keys() ​

keys() 方法用来获取 cache 的所有键。

js
cache.keys()

6.Clients ​

Clients 是在 Service Worker 中使用的 API。

可以通过 self.clients 来访问。

6-1.实例方法 ​

6-1-1.claim() ​

claim() 方法可以将一个激活的 Service Worker 在对应的作用域scope下设置为所有客户端 clients 的 controller。

该方法会触发 navigator.serviceWorker 上的 controllerchange 事件。

譬如以下代码,在 Service Worker 的 activate 事件中,调用 claim() 方法。

这会使得页面不必重新加载,就能将最新的 Service Worker 代码生效:

js
self.addEventListener('activate', event => {
  event.waitUntil(self.clients.claim())
})

TIP

Only the active worker can claim clients.

What is the use of self.clients.claim()?

6-1-2.get() ​

get() 方法能够在 clients 获取给定 id 所对应的 client。

js
const id = '778decd0-180b-4f2d-9e92-f4e45cecc305'

self.clients.get(id).then((client) => {
  self.clients.openWindow(client.url)
})

6-1-3.matchAll() ​

matchAll() 方法用来根据指定条件匹配 client 列表。

基础语法如下:

js
matchAll(options)

options 可配置:

  • includeUncontrolled 返回所有 Service Worker 客户端,即使还未受控制。默认值为 false。

  • type 客户端类型。默认值为 window。

    • window
    • worker
    • sharedworker
    • all

譬如:

js
self.clients.matchAll({
  includeUncontrolled: true,
  type: 'all'
}).then(clientList => {
  for (const client of clientList) {
    if (client.url === 'index.html') {
      clients.openWindow(client)
    }
  }
})

6-1-4.openWindow() ​

openWindow() 方法用来打开新的页面。

浏览器为了防止滥用,只有用户动作产生(有术语称作瞬时激活),才能调用此 API。否则会有 DOMException: Not allowed to open a window.。

js
// service worker
self.addEventListener('notificationclick', function(event) {
  event.waitUntil(
    // 打开新窗口
    clients.openWindow('/page-to-open')
      .then(function(client) {
        // 在这里使用新窗口的客户端对象
        if (client) {
          client.focus()
        }
      })
  )
})

7.Client ​

7-1.实例属性 ​

7-1-1.frameType ​

frameType 属性表明了 client 上下文类型。

常见的有如下几种类型:

  • auxiliary 表示辅助框架,通常对应于 <object> 或 <embed> 元素中的文档。

  • top-level 表示顶级框架,通常对应于整个页面或标签页。

  • nested 表示嵌套框架,通常对应于 iframe 中的文档。

7-1-2.id ​

id 属性表明了 client 唯一标识。

类似于 UUID 形式,譬如:e59bbf1c-69cf-44f2-a4e7-b28736041110。

7-1-3.type ​

type 属性表明了 client 类型。

常见的有如下几种类型:

  • window

  • worker

  • sharedworker

TIP

如果 type 是 window 的话,那么此 Client 可以被详细的称作 WindowClient。

WindowClient 继承自 Client,相对来说,多了 focused 及 focus() 等属性或方法。

7-1-4.url ​

url 属性表明了 client 的地址。

7-2.实例方法 ​

7-2-1.postMessage() ​

postMessage() 方法允许 Service Worker 向 Client (可能是 window、worker、sharedWorker) 传递消息。

js
addEventListener('fetch', (event) => {
  event.waitUntil(
    (async () => {
      // Exit early if we don't have access to the client.
      // Eg, if it's cross-origin.
      if (!event.clientId) return

      // Get the client.
      const client = await self.clients.get(event.clientId)
      // Exit early if we don't get the client.
      // Eg, if it closed.
      if (!client) return

      // Send a message to the client.
      client.postMessage({
        msg: 'Hey I just got a fetch from you!',
        url: event.request.url
      })
    })()
  )
})

8.ExtendableEvent ​

ExtendableEvent 是 Service Worker API 中的一个基类,用于表示可以被扩展的事件。

它是其他 Service Worker 事件的基类,包括 ExtendableMessageEvent、FetchEvent、InstallEvent、ActivateEvent 等,这些事件都继承自 ExtendableEvent。

ExtendableEvent 提供了一个 waitUntil() 方法,该方法接受一个 Promise,并延长事件的寿命,使得事件处理程序可以在 Promise 完成之前继续执行。

这使得在事件处理程序中执行异步操作成为可能,例如在 FetchEvent 中进行网络请求或在 InstallEvent 中预缓存资源。

8-1.实例方法 ​

8-1-1.waitUntil() ​

waitUntil() 是 Service Worker API 中的一个方法,主要用于在事件处理程序中延长事件的寿命。

js
self.addEventListener('install', function(event) {
  // 使用 waitUntil 方法接收一个 Promise
  event.waitUntil(
    // 执行异步操作,例如预缓存资源
    caches.open('v1').then(function(cache) {
      return cache.addAll([
        '/static/resource1',
        '/static/resource2'
        // 添加其他静态资源
      ])
    })
  )
})

在上述代码中,waitUntil() 接收了一个 Promise,该 Promise 是由 caches.open() 返回的。

只有在该 Promise 完成(即缓存操作完成)之前,Service Worker 才会认为 install 事件已经处理完成。

需要注意的是,如果传递给 waitUntil() 的 Promise 被拒绝(rejected),Service Worker 会认为事件处理失败,从而影响整个安装、激活或其他事件的生命周期。

9.ExtendableMessageEvent ​

ExtendableMessageEvent 是 Service Worker API 中的一个基类,它继承自 ExtendableEvent,用于表示从其他上下文(例如页面上下文)发送到 Service Worker 的消息事件。

这个类的主要用途是处理通过 postMessage API 发送的消息。

9-1.实例属性 ​

9-1-1.data ​

data 属性用来获取 postMessage 方法传递的数据信息。

js
// Main Thread
navigator.serviceWorker.controller.postMessage('Hello from the page!')

// Service Worker Thread
self.addEventListener('message', function(event) {
  // event 是 ExtendableMessageEvent 的实例
  console.log('Received message:', event.data)
})

9-1-2.lastEventId ​

lastEventId 属性用来获取最后一次接收的事件的标识符(ID)。

js
self.addEventListener('message', function(event) {
  // event 是 ExtendableMessageEvent 的实例
  const lastEventId = event.lastEventId
  console.log('Last Event ID:', lastEventId)
})

9-1-3.origin ​

origin 属性用来获取客户端来源 origin 地址。

js
self.addEventListener('message', function(event) {
  // event 是 ExtendableMessageEvent 的实例
  const origin = event.origin
  console.log('Origin:', origin)
})

9-1-4.ports ​

ports 属性用来获取与消息关联的 MessagePort 对象数组。

这个属性通常在与 postMessage API 和 MessageChannel 一起使用时发挥作用。

MessagePort 是一个双向通信通道,允许在两个上下文之间发送消息。

当通过 postMessage 发送消息时,如果消息中包含了 MessageChannel,那么与该消息关联的 MessagePort 对象就会被传递给接收方。

接收方可以通过 event.ports 属性来获取这些传递的 MessagePort 对象。

js
self.addEventListener('message', function(event) {
  // event 是 ExtendableMessageEvent 的实例
  const ports = event.ports
  console.log('Ports:', ports)
})

9-1-5.source ​

source 属性用来获取利用 postMessage 发送消息的 Client。

js
self.addEventListener('message', function(event) {
  // event 是 ExtendableMessageEvent 的实例
  const source = event.source
  console.log('Source:', source)
})

10.FetchEvent ​

FetchEvent 是在 Service Worker 中拦截和处理 fetch 请求或者资源网络请求(JS、CSS 等)的事件类型。

该 API 提供了 event.respondWith() 方法,来供开发者自定义响应。

10-1.实例属性 ​

10-1-1.clientId ​

clientId 属性用来获取当前 Service Worker 控制着的 Client 标识。

js
self.addEventListener('fetch', (event) => {
  console.log(event.clientId)
})

10-1-2.replacesClientId ​

replacesClientId 属性用来获取在页面导航期间被替换的客户端 ID。

假如,从 pageA 导航到 pageB,则 replacesClientId 是与 pageA 相关联的客户端 ID。

如果从 about:blank 页面导航到 pageB,则 replacesClientId 是一个空字符串。因为 about:blank 页面是会被重用,而不是替换。

另外,如果请求不属于浏览器导航(譬如是 ajax 或 fetch),那么 replacesClientId 也会是一个空字符串。

js
self.addEventListener('fetch', (event) => {
  console.log(event.replacesClientId)
})

10-1-3.resultingClientId ​

resultingClientId 属性用来获取在页面导航期间被替换后的客户端 ID。

假如,从 pageA 导航到 pageB,则 resultingClientId 是与 pageB 相关联的客户端 ID。

js
self.addEventListener('fetch', (event) => {
  console.log(event.resultingClientId)
})

10-1-4.handled ​

handled 属性用来获取 FetchEvent 已经被处理完后的节点。

该属性会返回一个 promise,当该 promise 的状态改变,证明 FetchEvent 已经被处理。

js
self.addEventListener('fetch', (event) => {
  event.respondWith(
    (async function () {
      const response = await doCalculateAResponse(event.request)

      event.waitUntil(
        (async function () {
          await doSomeAsyncStuff() // optional

          // Wait for the event to be consumed by the browser
          await event.handled

          return doFinalStuff() // Finalize AFTER the event has been consumed
        })()
      )

      return response
    })()
  )
})

10-1-5.request ​

request 属性用来获取 fetch 事件的请求对象。

js
self.addEventListener('fetch', (event) => {
  console.log(event.request)
})

10-1-6.preloadResponse ​

preloadResponse 属性用来获取 preload 预加载响应。

js
self.addEventListener('fetch', (event) => {
  event.respondWith(
    (async () => {
      // Respond from the cache if we can
      const cachedResponse = await caches.match(event.request)
      if (cachedResponse) return cachedResponse

      // Else, use the preloaded response, if it's there
      const response = await event.preloadResponse
      if (response) return response

      // Else try the network.
      return fetch(event.request)
    })()
  )
})

10-2.实例方法 ​

10-2-1.respondWith() ​

respondWith() 方法用于提供自定义的响应,以替代默认的网络请求。

基础语法如下:

js
event.respondWith(response)

其中 response 是一个表示要返回的响应的对象。它可以是来自网络的真实响应,也可以是通过 Response 构造函数创建的自定义响应,也可以是一个 resolved 结果为 Response 的 Promise。

js
self.addEventListener('fetch', function(event) {
  // 尝试从缓存中获取匹配的响应
  event.respondWith(
    caches.match(event.request).then(function(cachedResponse) {
      // 如果缓存中存在匹配的响应,直接返回缓存
      if (cachedResponse) {
        return cachedResponse
      }

      // 否则,发起网络请求并将响应添加到缓存
      return fetch(event.request).then(function(networkResponse) {
        // 需要克隆响应对象,因为它是流式的,只能读取一次
        const clonedResponse = networkResponse.clone()

        // 将网络响应添加到缓存
        caches.open('my-cache').then(function(cache) {
          cache.put(event.request, clonedResponse)
        })

        // 返回网络响应
        return networkResponse
      })
    })
  )
})

11.InstallEvent(Deprecated) ​

MDN上显示该**构造函数 API**已经废弃了,因此不再过多介绍。

在 Service Worker 中的 install 中可以打印 InstallEvent 实例如下: