大前端百科全书浏览器专题之service worker

·  阅读 141
大前端百科全书浏览器专题之service worker

1. 概念

提到 Service Worker ,不得不先介绍一下 Web Worker,众所周知 javaScript 是执行在单线程的,如果执行大量计算任务就会堵塞了前端的渲染。而通过独立的线程的能力,Web Worker 可以分解耗时的任务。但是它的功能不应只局限于此,Service Worker 便是在 Web Worker 的基础上增加了离线缓存的能力。

Service Worker 可以拦截处理页面的所有网络请求,可以使用 cache 和 indexDB 的 api,可以让开发者自己主动管理缓存的内容以及版本,为离线弱网环境下的 web 的运行提供了可能,让 web 在体验上更加贴近 native

2. 特点

  • 独立于主线程、在后台运行的脚本
  • install 后就永远存在,除非被手动卸载
  • 可编程拦截请求和返回,缓存文件。sw可以通过 fetch 这个api,来拦截网络和处理网络请求,再配合cacheStorage来实现web页面的缓存管理以及与前端postMessage通信。
  • 不能直接操纵dom:因为sw是个独立于网页运行的脚本,所以在它的运行环境里,不能访问窗口的window以及dom。
  • 必须是https的协议才能使用。不过在本地调试时,在http://localhosthttp://127.0.0.1 下也是可以跑起来的。
  • 异步实现,sw大量使用promise。

一开始我以为 Service Worker 就是用来做离线应用的,后来渐渐研究才发现不是这样的。→ Service Worker 只是一个常驻在浏览器中的 JS 线程,它本身做不了什么。它能做什么,全看跟哪些 API 搭配使用

  1. 跟 Fetch 搭配,可以从浏览器层面拦截请求,做数据 mock;
  2. 跟 Fetch 和 CacheStorage 搭配,可以做离线应用;
  3. 跟 Push 和 Notification 搭配,可以做像 Native APP 那样的消息推送,这方面可以参考 villainhr 的文章:Web 推送技术
  4. ...

假如把这些技术融合在一起,再加上 Manifest 等,就差不多成了 PWA 了。

总之,Service Worker 是一种非常关键的技术,有了它,我们能更接近浏览器底层,能做更多的事情。

3. 生命周期

service worker从代码的编写,到在浏览器中的运行,主要经过下面几个阶段 installing -> installed -> activating -> activated -> redundant;

installing: 这个状态发生在service worker注册之后,表示开始安装。在这个过程会触发install事件回调指定一些静态资源进行离线缓存。

installed:sw已经完成了安装,进入了waiting状态,等待其他的Service worker被关闭(在install的事件回调中,可以调用skipWaiting方法来跳过waiting这个阶段)

activating: 在这个状态下没有被其他的 Service Worker 控制的客户端,允许当前的 worker 完成安装,并且清除了其他的 worker 以及关联缓存的旧缓存资源,等待新的 Service Worker 线程被激活。

activated: 在这个状态会处理activate事件回调,并提供处理功能性事件:fetch、sync、push。(在acitive的事件回调中,可以调用self.clients.claim())

redundant: 废弃状态,这个状态表示一个sw的使命周期结束

4. 使用场景

4.1 预加载

  • 页面开启 ServiceWorker 线程独立去下载未来页面可能会用到的资源,放在缓存中。可以认为未来的页面为高频应用场景,提前缓存。

4.2 缓存

性能的提升是相对于完全没有缓存的情况来讲的,而浏览器本身有着相对完善的HTTP缓存机制。

所以使用Service Worker缓存,并不能使我们已经相对完善的架构有立竿见影的性能提升,Service Worker缓存真正有意义的地方在于,利用它可以更精准地、以编码方式控制缓存,如何缓存、缓存什么、如何更新缓存,完全取决于代码如何写,所以这提供了很大的自由度,但同时也带来维护成本。它只是换了一种缓存方式,而不是从无到有的突破。

4.3 离线体验

既然我们现在具有了离线缓存文件和拦截HTTP请求的能力,那我们可以在Service Worker安装时,缓存一个offline.html,类似于404页面。离线状态下,我们访问index.html文件的请求是会失败的,在这个时机,我们返回offline.html文件展示给用户,至于具体展示些什么,取决于具体需求,甚至可以像chrome离线时那样,做一个小游戏来调戏没有网络的用户。

5. 代码实现

  • 主线程注册
//在页面代码里面监听onload事件,使用sw的配置文件注册一个service worker
 if ('serviceWorker' in navigator) {
    window.addEventListener('load', function () {
        navigator.serviceWorker.register('serviceWorker.js')
            .then(function (registration) {
                // 注册成功
                console.log('ServiceWorker registration successful with scope: ', registration.scope);
            })
            .catch(function (err) {
                // 注册失败
                console.log('ServiceWorker registration failed: ', err);
            });
    });
}
复制代码
  • worker
//serviceWorker.js
var CACHE_NAME = 'my-first-sw';
var urlsToCache = [
    '/',
    '/styles/main.css',
    '/script/main.js'
];

self.addEventListener('install', function(event) {
    // 在install阶段里可以预缓存一些资源
    event.waitUntil(
        caches.open(CACHE_NAME)
            .then(function(cache) {
                console.log('Opened cache');
                return cache.addAll(urlsToCache);
            })
    );
});

//在fetch事件里能拦截网络请求,进行一些处理
self.addEventListener('fetch', function (event) {
    event.respondWith(
        caches.match(event.request).then(function (response) {
            // 如果匹配到缓存里的资源,则直接返回
            if (response) {
                return response;
            }
          
            // 匹配失败则继续请求
            var request = event.request.clone(); // 把原始请求拷过来

            //默认情况下,从不支持 CORS 的第三方网址中获取资源将会失败。
            // 您可以向请求中添加 no-CORS 选项来克服此问题,不过这可能会导致“不透明”的响应,这意味着您无法辨别响应是否成功。
            if (request.mode !== 'navigate' && request.url.indexOf(request.referrer) === -1) 						{
                request = new Request(request, { mode: 'no-cors' })
            }

            return fetch(request).then(function (httpRes) {
								//拿到了http请求返回的数据,进行一些操作
              
              	//请求失败了则直接返回、对于post请求也直接返回,sw不能缓存post请求
                if (!httpRes  || ( httpRes.status !== 200 && httpRes.status !== 304 && httpRes.type !== 'opaque') || request.method === 'POST') {
                    return httpRes;
                }

                // 请求成功的话,将请求缓存起来。
                var responseClone = httpRes.clone();
                caches.open('my-first-sw').then(function (cache) {
                    cache.put(event.request, responseClone);
                });

                return httpRes;
            });
        })
    );
});
复制代码
分类:
前端
标签:
分类:
前端
标签: