Service Worker 离线缓存构建详解

Nenge123 ServiceWorker

示例代码

sw.js只会拦截当前目录或者后代目录的http请求,如果要缓存根目录文件(index.html),必须把sw.js放在根目录.

    if ('serviceWorker' in navigator) {
        navigator.serviceWorker.register('/sw.js').then(worker => {
        }).catch(e => console.log('reg errot', e));
    }

sw.js

var CACHE_PREX = 'DQM_'; //定义一个缓存名前缀
var CACHE_NAME = CACHE_PREX+'v4';//定义缓存版本
var CACHE_PATH = serviceWorker.scriptURL.split('/').slice(0,-1).join('/')+'/'; //获取URL目录
var urlsToCache = [//定义要缓存的文件列表,可选,不设置也可以.具体看 fetch
    'favicon.ico',
    'assets/dqm.png',
];
Object.entries(
    {
        install(event){
            //注册,如果本脚本发生改变 会重新注册
            console.log('serviceWorker install');
            return self.skipWaiting();//跳过等待 如果不跳过则可以进行预先缓存
            event.waitUntil(
                caches.open(CACHE_NAME).then(
                    cache=>cache.addAll(urlsToCache) 
                    //这里意思是立即吧缓存列表下载下来.这样缺点是屏幕会根据时间长时间空白,但是确保所有文件都缓存了.
                    //这里的缓存是可以跨目录的,而是fetch事件只能拦截sw.js所在目录的的请求.
                    //对于新手来说就会出现明明缓存了,离线失败
                    //对于老手来说,这里换成一个奇怪文件A,读取B url时返回A内容  fetch事件:response = await (await caches.open(CACHE_NAME)).get(A url)
                ).then(() => {
                    console.log('Cache downloaded',caches)
                    self.skipWaiting()
                })
            );
        },
        activate(event){
            //激活变化 初始化
            //清空特定数据const cache = await caches.open(CACHE_NAME);cache.delete(url);
            console.log('serviceWorker activate');
            event.waitUntil(
                caches.keys().then(function (cacheNames) {
                    return Promise.all(
                        cacheNames.map(function (cacheName) {
                            if (CACHE_NAME != cacheName&&cacheName.includes(CACHE_PREX)) {
                                //移除特定旧缓存数据库
                                return caches.delete(cacheName);
                            }
                        })
                    );
                })
            );
        },
        fetch(event){
            //拦截请求 event.request 一个请求对象
            return event.respondWith(new Promise(async resolve=>{
                var url = event.request.url.replace(CACHE_PATH,''),cacheTime;
                var response = await caches.match(event.request); //看看缓存里面有没有我缓存的文件
                if(navigator.onLine){
                    //联网状态
                    if(response){
                        /*
                        //缓存里面找到数据
                        //response.headers.get('date') 缓存里面的保存日期
                        if(new Date()  - Date.parse(response.headers.get('date'))>86400){
                            fetch(event.request).then(async res=>await caches.open(CACHE_NAME).put(event.request, res.clone()))
                            // 后台更新 当超过缓存时间时,后天刷新记录,
                        }
                        */
                       /*
                        //当然用户刷新浏览器时采用新数据,当然你也可以立即应用新的(如果文件较大推荐后台更新) 
                         if(new Date()  - Date.parse(response.headers.get('date'))>86400) response = null;
                       */
                    }
                    if(!response){
                        //没有匹配到数据 进行正常下载
                        response =  await fetch(event.request);
                        if(urlsToCache.includes(event.request.url.replace(CACHE_PATH,''))){
                            //如果 install 中已经缓存,这一步可以删掉.
                            //缓存条件 这里的缓存条件就是我上面的缓存列表,
                            //当然你可以额外增加图片后缀一律缓存亦可
                            const cache = await caches.open(CACHE_NAME); //打开缓存表  
                            console.log('[Service Worker] Caching new resource: ' + url);
                            cache.put(event.request, response.clone()); //写入缓存 其他方法match get keys(都是异步)
                            //这里的 表cache.match与caches.match不一样的,前者只有数据,后者含有请求的headers
                        }
                    }
                }
                resolve(response);//返回数据

            }));
        },
        message(event){
            console.log(event.data,event.source);
            //处理接到的信息
            //对信息源通信 event.source.postMessage() 
            //注意如果使用这个通信 ,不要使用 worker.postMessage/self.postMessage  否则会陷入无限循环,因为他也会触发message
            //主线程用 navigator.serviceWorker.controller.postMessage 发给worker信息
            //主线程 navigator.serviceWorker.addEventListener('message') 接收
            //所以woker是不能主动对主线程发起通信.
        }
    }
).forEach(
    entry=>{
        self.addEventListener(entry[0],entry[1]);
    }
);

caches与indexedb

caches也是一种缓存,但是储存请求. caches.match(url)匹配一个请求,而存储时需要先打开一个版本,再去写入,如果存在不同版本则先打开版本再去match.

match的结果包含数据headers,而cache.get获取的只有数据流的对象.

    const cache = await caches.open(CACHE_NAME);
    const request = await fetch(url);
    cache.put(request.url, request.clone()); 
    cache.match(url);

评论