前端开发者简历中经常会写熟悉HTML5,但是面试中一问service worker的内容,就容易翻车,现在就来总结一下。
简介
W3C组织早在2014年5月就提出过Service Worker这样的一个HTML5 API ,主要用来做持久的离线缓存。
浏览器中js运行在单一主线程中,同一时间只能做一件事情。 如果一段代码运算太过耗时,就会一直占用浏览器主线程,造成性能下降。基于这个问题,W3C提出了web Worker,将耗时太长的任务交给web worker,然后通过post Message告诉主线程,主线程通过onMessage得到结果。但是web Worker是临时的,每次运行的结果不能持久的保持下来,下次有复杂的运算,还需要重新计算一次。为了解决这个问题,推出了Service Worker,相对于web worker增加了离线缓存能力。
Service Worker是一个事件驱动worker,运行在一个单独的后台进程,是PWA(ProgressiveWeb App)运行的基础。主要用于代理网页请求,可缓存请求结果;可实现离线缓存功能,也拥有单独的作用域范围和运行环境。我们以后把Service Worker简称为SW。
SW使用限制和主要作用
- 使用限制:SW除了work线程的限制外,由于可拦截页面请求,为了保证页面安全,浏览器端对sw的使用限制也不少。
1、无法直接操作DOM对象,也无法访问window、document、parent对象。可以访问navigator、location; SW 通过响应 postMessage 接口发送的消息来与其控制的页面通信,页面可在必要时对 DOM 执行操作。
2、可代理的页面作用域限制。默认是sw.js所在文件目录及子目录的请求可代理,可在注册时手动设置作用域范围;
3、必须在 https 中使用,允许在开发调试的localhost使用。 - SW主要作用
1、可以用来做缓存,以达到提升体验、节省浏览等等
2、SW 是一种可编程网络代理,让您能够控制页面所发送网络请求的处理方式。
3、离线缓存接口请求及文件,更新、清除缓存内容;
4、可分配给 Service Worker 一些任务,并在使用基于 Promise 的方法当任务完成时收到结果。
5、Service Worker处于空闲状态会被终止,在下一次需要时重启。
SW的生命周期
SW 的生命周期完全独立于网页。SW为网页添加一个类似于 App 的生命周期,它只会响应系统事件,就算浏览器关闭时操作系统也可以唤醒 SW,这点非常重要,让Web App与 Native App 的能力变得类似了。由于是离线缓存,所以在初始安装时、更新它们的所走的生命周期是不相同。下面我们就以初始安装时结合代码来分析它的执行步骤。
SW的生命周期大致分为:注册、更新、安装成功、安装失败、激活、销毁。
SW的使用
- 使用SW前提条件
1、必须在 https 中使用,允许在开发调试的localhost使用。
2、浏览器必须支持SW -
初始安装时:大致可以分为注册SW => 安装SW => 激活 => 空闲 => (缓存和返回请求/终止),在初始安装时会大致分为这几个步骤,下面就按照这几个步结合代码实现。流程图如下:
1、注册 Service Worker
用户首次访问SW控制的网站或页面时,sw.js会立刻被下载和解析。我们要在页面中写入JavaScript来注册SW。
// 判断浏览器是否支持serviceWorker
// 首先检浏览器是否支持SW,如果支持就在浏览器加载后通过register().then注册sw.js,并且设置注册成功或者失败的回调函数。
if ('serviceWorker' in navigator) {
// 在页面加载后
window.addEventListener('load', function () {
// 通过navigator.serviceWorker.register 注册'./sw.js
navigator.serviceWorker.register('./sw.js')
.then(reg => { //注册成功
console.log('注册成功', reg)
}).catch(err => { //注册成功
console.log('注册失败', err)
})
});
} else {
console.log('当前浏览器不支持SW')
}
2、安装 Service Worker
在受控页面启动注册流程后,下面就是SW获取的第一个事件install,并且只发生一次。传递到 installEvent.waitUntil() 的一个 promise 可表明安装的持续时间以及安装是否成功。在install中要做三件事打开缓存、缓存文件、确认所有需要的资产是否已缓存。
// 在sw.js中监听对应的安装事件,并预处理需要缓存的文件
// 该部分内容涉及到cacheStorage API
// 定义缓存空间名称
const CACHE_NAME = 'sw_cache_v1';
// 定义需要缓存的文件目录
let cachelist = ['./app.js', './index.css'];
// 监听安装事件,返回installEvent对象
self.addEventListener('install', function (installEvent) {
// waitUntil方法执行缓存方法
installEvent.waitUntil(
// cacheStorage API 可直接用caches来替代
// open方法创建/打开缓存空间,并会返回promise实例
// then来接收返回的cache对象索引
caches.open(CACHE_NAME)
// cache对象addAll方法解析(同fetch)并缓存所有的文件
.then(function(cache) {
console.log('Opened cache');
return cache.addAll(cachelist);
})
);
});
3、激活
SW 准备控制客户端并处理 push 和 sync 等功能事件时,您将获得一个 activate 事件。但这不意味着调用 .register() 的页面将受控制。如果第二次加载此演示(换言之,刷新页面),该页面将受控制。改写代码sw.js如下:
self.addEventListener('install', () => {
// 一般注册以后,激活需要等到再次刷新页面后再激活
// 可防止出现等待的情况,这意味着服务工作线程在安装完后立即激活
self.skipWaiting();
})
self.addEventListener('activate', function (event) {
event.waitUntil(
// cacheStorage API 可直接用caches来替代
// open方法创建/打开缓存空间,并会返回promise实例
// then来接收返回的cache对象索引
caches.open(CACHE_NAME)
// cache对象addAll方法解析(同fetch)并缓存所有的文件
.then(function(cache) {
console.log('Opened cache');
return cache.addAll(cachelist);
})
);
})
4、clients.claim
激活 SW
后,您可以通过在其中调用 clients.claim()
控制未受控制的客户端。google developer中的一个异步加载图片的实例。下面修改代码如下:
self.addEventListener('install', (event) => {
event.waitUntil(
// cacheStorage API 可直接用caches来替代
// open方法创建/打开缓存空间,并会返回promise实例
// then来接收返回的cache对象索引
caches.open(CACHE_NAME)
// cache对象addAll方法解析(同fetch)并缓存所有的文件
.then(function(cache) {
console.log('Opened cache');
return cache.addAll(cachelist);
})
);
// 一般注册以后,激活需要等到再次刷新页面后再激活
// 可防止出现等待的情况,这意味着服务工作线程在安装完后立即激活
self.skipWaiting();
})
self.addEventListener('activate', function (event) {
// 若缓存数据更改,则在这里更新缓存
var cacheDeletePromise = caches.keys()
.then(keyList => {
Promise.all(keyList.map(key => {
if (key !== CACHE_NAME) {
var deletePromise = caches.delete(key)
return deletePromise
} else {
Promise.resolve()
}
}));
});
event.waitUntil(
Promise.all([cacheDeletePromise]).then(res => {
this.clients.claim()
})
);
})
5、缓存和返回请求
上我们已经安装并且激活了SW,现在我们要返回一个缓存的响应。SW用户转至其他页面或刷新当前页面后,将开始接受fetch事件。
self.addEventListener('fetch', function(event) {
event.respondWith(
caches.match(event.request)
.then(function(response) {
// Cache hit - return response
if (response) {
return response;
}
return fetch(event.request);
})
);
});
网友评论