Skip to content

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

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

1.Main Thread

Main Thread 中涉及到的顶级 APIServiceWorkerContainer

ServiceWorkerContainer 继承自 EventTarget

1-1.navigator.serviceWorker

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

js
console.log(navigator.serviceWorker instanceof ServiceWorkerContainer)

1-2.实例属性

1-2-1.controller

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

如果 navigator.serviceWorkerstateactivatingactivated,那么 navigator.serviceWorker.controller 属性会返回一个 serviceWorker 对象。

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

js
console.log(navigator.serviceWorker.controller)

1-2-2.ready

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

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

如果 promiseresolved 则会返回 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 服务类型,classicmodule
    • 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 中的 postMessageMain Thread 传递消息时,Main Thread 会在 DOMContentLoaded 加载完成后,逐个处理 message

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

js
navigator.serviceWorker.startMessages()

1-4.事件监听

1-4-1.controllerchange

Main Thread 中监听 Main Threadnavigator.serviceWorkercontroller 发生改变。

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() 方法注册之后,会返回 ServiceWorkerRegistrationregistration 实例对象。

ServiceWorkerRegistration 同样继承自 EventTarget

2-1.实例属性

2-1-1.active

该属性会返回 stateactivatingactivatedServiceWorker

默认为 null

2-1-2.installing

该属性会返回 stateinstallingServiceWorker

默认为 null

2-1-3.waiting

该属性会返回 stateinstalledServiceWorker

默认为 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 处于 activatingactivated 状态时触发。

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 发生网络请求时触发,不限于 ajaxfetch 或者静态资源请求(htmljscss 等等)。

通过监听 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,则 promiseresolved 结果是 true

如果在 CacheStorage 中未找到指定的 Cache,则 promiseresolved 结果是 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 对象或者 URLresponse 关联

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下设置为所有客户端 clientscontroller

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

譬如以下代码,在 Service Workeractivate 事件中,调用 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

如果 typewindow 的话,那么此 Client 可以被详细的称作 WindowClient

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

7-1-4.url

url 属性表明了 client 的地址。

7-2.实例方法

7-2-1.postMessage()

postMessage() 方法允许 Service WorkerClient (可能是 windowworkersharedWorker) 传递消息。

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

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

它是其他 Service Worker 事件的基类,包括 ExtendableMessageEventFetchEventInstallEventActivateEvent 等,这些事件都继承自 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

ExtendableMessageEventService 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 APIMessageChannel 一起使用时发挥作用。

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 请求或者资源网络请求JSCSS 等)的事件类型。

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 页面是会被重用,而不是替换。

另外,如果请求不属于浏览器导航(譬如是 ajaxfetch),那么 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 结果为 ResponsePromise

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 实例如下: