ServiceWorker 缓存离线化

概述

Service Worker 是HTML5 的一个新特性,主要用来做持久的离线缓存

为什么要使用ServiceWorker

在公司业务中,因为经常要处理性能优化方面的需求,使用传统的性能优化手段,满足了大多数业务场景。但是,如果目标用户手机性能以及网络普遍较差的情况下(例如东南亚、印度等海外市场),瓶颈就在于DNS查询,TCP的建立时间,采用常规的优化手段就显得捉襟见肘。此时,我们项目组有尝试采用离线缓存方案,即将静态资源缓存到本地,通过拦截代理请求,读取本地文件,加快访问速度。

ServiceWorker的目的

这个 API 的唯一目的就是解放主线程,Web Worker 是脱离在主线程之外的,将一些复杂的耗时的活交给它干,完成后通过 postMessage 方法告诉主线程,而主线程通过 onMessage 方法得到 Web Worker 的结果反馈。

功能和特性

  • Service Worker拥有自己独立的 worker 线程,独立于当前网页线程
  • 离线缓存静态资源
  • 拦截代理请求和响应
  • 可自定义响应内容
  • 可以通过postMessage向主线程发送消息
  • 无法直接操作DOM
  • 必须在HTTPS环境下工作或 localhost / 127.0.0.1 (自身安全机制)
  • 通过Promise异步实现
  • Service Worker安装(installing)完成后,就会一直存在,除非手动卸载(unregister)

生命周期

Service Worker 的生命周期完全独立于网页

  • 注册 (register)
  • 安装 (install)
  • 激活 (activate)

通常使用 service worker 只需要以下几个步骤:

  • 1. 检测是否支持serivceworker

首先,检测当前环境是否支持 service worker,可以使用 'serviceWorker' in navigator 进行检测。

  • 2. 注册(register)

如果支持,可以使用 navigator.serviceWorker.register('./sw.js'),在当前主线程中注册 service worker。如果注册成功,service worker 则在 ServiceWorkerGlobalScope环境中运行; 需要注意的是: 当前环境无法操作DOM,且和主线程之间相互独立(即线程之间不会相互阻塞)。

  • 3. 安装(install)

然后,后台开始安装service worker,一般在此过程中,开始缓存一些静态资源文件。

  • 4. 激活(active)

安装成功之后,准备进行激活 service worker,通常在激活状态下,主要进行缓存清理,更新service worker等操作。

  • 5. 使用(activing)

激活成功后,,service worker 就可以控制当前页面了。需要注意的是,只有在service worker成功激活后,才具有控制页面的能力,一般在第一次访问页面时,service worker第一次创建成功,并没有激活,只有当刷新页面,再次访问之后,才具有控制页面的能力。

  • 6. 卸载(unregister)

缓存颗粒化

  1. 缓存 *.html 静态资源文件

缓存收益和成本

  • 收益
    1. 省去建立tcp的连接时长,加快首屏加载速度
    2. 减少静态资源服务器的负载
  • 成本
    1. 数据不一致问题(更新策略)
    2. 代码维护成本(缓存文件)

项目演示

本项目在第一次安装serverworker之后,可以在控制台看到以下信息:

查看对应请求的静态资源信息:

可以看到,第一次安装serviceworker时,读取到的静态资源并没有缓存。

刷新浏览器之后,我们再看一下这些静态资源:

可以看到,静态资源以及被serviceworker缓存起来了。

我们再来查看当前serviceworker安装情况:

可以看到,serviceworker已经处于激活状态。

最后,看一下离线功能的效果:

是不是很神奇,在离线状态下我们的页面也是能够展示出数据的。 其中,离线的原理就是利用了serviceworker中,fetchcacheStorage这两个接口,将请求进行拦截,将响应进行缓存

源码实现

该源码实现了以下几个功能:

  • 强制更新 通过self.skipWaiting(),如果检测到新的service worker文件,就会立即替换掉旧的。
  • 缓存静态资源 cache.addAll(cacheFiles) 通过这个接口实现
  • 拦截请求 通过监听fetch事件,可以拦截当前页所有请求self.addEventListener('fetch',function(e){})
  • 缓存响应 将响应内容加入缓存cache.put(evt.request, response)
// 缓存静态资源文件列表
let cacheFiles = [
  './test.js',
  './index.html',
  './src/img/yy.png'
]
// serviceworker使用版本
let __version__ = 'cache-v2'

// 缓存静态资源
self.addEventListener('install', function (evt) {
  // 强制更新sw.js
  self.skipWaiting()
  evt.waitUntil(
    caches.open(version).then(function (cache) {
      return cache.addAll(cacheFiles)
    })
  )
})

// 缓存更新
self.addEventListener('active', function (evt) {
  evt.waitUntil(
    caches.keys().then(function (cacheNames) {
      return Promise.all(
        cacheNames.map(function (cacheName) {
          if (cacheName !== version) {
            return caches.delete(cacheName)
          }
        })
      )
    })
  )
})

// 请求拦截
self.addEventListener('fetch', function (evt) {
  console.log('处理fetch事件:', evt.request.url)
  evt.respondWith(
    caches.match(evt.request).then(function (response) {
      if (response) {
        console.log('缓存匹配到res:', response)
        return response
      }
      console.log('缓存未匹配对应request,准备从network获取', caches)
      return fetch(evt.request).then(function (response) {
        console.log('fetch获取到的response:', response)
        caches.open(version).then(function (cache) {
          cache.put(evt.request, response)
          return response
        })
      })
    }).catch(function (err) {
      console.error('fetch 接口错误', err)
      throw err
    })
  )
})

复制代码

请参考: 源码地址

转载于:https://juejin.im/post/5c74e5566fb9a049be5e23c0

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值