阅读目录javascript
一:什么是离线优先?php
传统的web应用彻底依赖于服务器端,好比像很早之前jsp,php,asp时代,全部的数据,内容和应用逻辑都在服务器端,客户端仅仅作一些html内容渲染到页面上去。可是随着技术在不断的改变,如今不少业务逻辑也放在前端,先后端分离,前端是作模板渲染工做,后端只作业务逻辑开发,只提供数据接口。可是咱们的web前端开发在数据层这方面来说仍是依赖于服务器端。若是网络中断或服务器接口挂掉了,都会影响数据页面展现。所以咱们须要使用离线优先这个技术来更优雅的处理这个问题。css
拥抱离线优先的真正含义是:尽管应用程序的某些功能在用户离线时可能不能正常使用,可是更多的功能应该保持可用状态。html
离线优先它能够优雅的处理这些异常状况下问题,当用户离线时,用户正在查看数据多是以前的数据,可是仍然能够访问以前的页面,以前的数据不会丢失,这就意味着用户能够放心使用某些功能。那么要作到离线时候还能够访问,就须要咱们缓存哦。前端
二:经常使用的缓存模式java
在为咱们的网站使用缓存以前,咱们须要先熟悉一些常见的缓存设计模式。若是咱们要作一个股票K线图的话,由于股票数据是实时更新的,所以咱们须要实时的去请求网络最新的数据(固然实时确定使用websocket技术,而不是http请求,我这边是假如)。只有当网络请求失败的时候,咱们再从缓存里面去读取数据。可是对于股票K线图中的一些图标展现这样的,由于这些图标是通常不会变的,因此咱们更倾向于使用缓存里面的数据。只有在缓存里面找不到的状况下,再从网络上请求数据。node
因此有以下几种缓存模式:webpack
1. 仅缓存
2. 缓存优先,网络做为回退方案。
3. 仅网络。
4. 网络优先,缓存做为回退方案。
5. 网络优先,缓存做为回退方案, 通用回退。git
1. 仅缓存github
什么是仅缓存呢?仅缓存是指 从缓存中响应全部的资源请求,若是缓存中找不到的话,那么请求就会失败。那么仅缓存对于静态资源是实用的。由于静态资源通常是不会发生变化,好比图标,或css样式等这些,固然若是css样式发生改变的话,在后缀能够加上时间戳这样的。好比 base.css?t=20191011 这样的,若是时间戳没有发生改变的话,那么咱们直接从缓存里面读取。
所以咱们的 sw.js 代码能够写成以下(注意:该篇文章是在上篇文章基础之上的,若是想看上篇文章,请点击这里:
self.addEventListener("fetch", function(event) { event.respondWith( caches.match(event.request) ) });
如上代码,直接监听 fetch事件,该事件能监听到页面上全部的请求,当有请求过来的时候,它使用缓存里面的数据依次去匹配当前的请求,若是匹配到了,就拿缓存里面的数据,若是没有匹配到,则请求失败。
2. 缓存优先,网络做为回退方案
该模式是:先从缓存里面读取数据,当缓存里面没有匹配到数据的时候,service worker才会去请求网络并返回。
代码变成以下:
self.addEventListener("fetch", function(event) { event.respondWith( caches.match(event.request).then(function(response) { return response || fetch(event.request); }) ) });
如上代码,使用fetch去监听全部请求,而后先使用缓存依次去匹配请求,不论是匹配成功仍是匹配失败都会进入then回调函数,当匹配失败的时候,咱们的response值就为 undefined,若是为undefined的话,那么就网络请求,不然的话,从拿缓存里面的数据。
3. 仅网络
传统的web模式,就是这种模式,从网络里面去请求,若是网络不通,则请求失败。所以代码变成以下:
self.addEventListener("fetch", function(event) { event.respondWith( fetch(event.request) ) });
4. 网络优先,缓存做为回退方案。
先从网络发起请求,若是网络请求失败的话,再从缓存里面去匹配数据,若是缓存里面也没有找到的话,那么请求就会失败。
所以代码以下:
self.addEventListener("fetch", function(event) { event.respondWith( fetch(event.request).catch(function() { return caches.match(event.request); }) ) });
5. 网络优先,缓存做为回退方案, 通用回退
该模式是先请求网络,若是网络失败的话,则从缓存里面读取,若是缓存里面读取失败的话,咱们提供一个默认的显示给页面展现。
好比显示一张图片。以下代码:
self.addEventListener("fetch", function(event) { event.respondWith( fetch(event.request).catch(function() { return caches.match(event.request).then(function(response) { return response || caches.match("/xxxx.png"); }) }); ) });
三:混合与匹配,创造新模式
上面是咱们五种缓存模式。下面咱们须要将这些模式要组合起来使用。
1. 缓存优先,网络做为回退方案, 并更新缓存。
对于不常常改变的资源,咱们能够先缓存优先,网络做为回退方案,第一次请求完成后,咱们把请求的数据缓存起来,下次再次执行的时候,咱们先从缓存里面读取。
所以代码以下:
self.addEventListener("fetch", function(event) { event.respondWith( caches.open("cache-name").then(function(cache) { return cache.match(event.request).then(function(cachedResponse){ return cachedResponse || fetch(event.request).then(function(networkResponse){ cache.put(event.request, networkResponse.clone()); return networkResponse; }); }) }) ) });
如上代码,咱们首先打开缓存,而后使用请求匹配缓存,无论匹配成功了仍是匹配失败了,都会进入then回调函数,若是匹配到了,说明缓存里面有对应的数据,那么直接从缓存里面返回,若是缓存里面 cachedResponse 值为undefined,没有的话,那么就从新使用fetch请求网络,而后把请求的数据 networkResponse 从新返回回来,而且克隆一份 networkResponse 放入缓存里面去。
2. 网络优先,缓存做为回退方案,并频繁更新缓存
若是一些常常要实时更新的数据的话,好比百度上的一些实时新闻,那么都须要对网络优先,缓存做为回退方案来作,那么该模式下首先会从网络中获取最新版本,当网络请求失败的时候才回退到缓存版本,当网络请求成功的时候,它会将当前返回最新的内容从新赋值给缓存里面去。这样就保证缓存永远是上一次请求成功的数据。即便网络断开了,仍是会使用以前最新的数据的。
所以代码能够变成以下:
self.addEventListener("fetch", function(event) { event.respondWith( caches.open("cache-name").then(function(cache) { return fetch(event.request).then(function(networkResponse) { cache.put(event.request, networkResponse.clone()); return networkResponse; }).catch(function() { return caches.match(event.request); }); }) ) });
如上代码,咱们使用fetch事件监听全部的请求,而后打开缓存后,咱们先请网络请求,请求成功后,返回最新的内容,此时此刻同时把该返回的内容克隆一份放入缓存里面去。可是当网络异常的状况下,咱们就匹配缓存里面最新的数据。可是在这种状况下,若是咱们第一次网络请求失败后,因为第一次咱们没有作缓存,所以缓存也会失败,最后就会显示失败的页面了。
3. 缓存优先,网络做为回退方案,并频繁更新缓存
对于一些常常改变的资源文件,咱们能够先缓存优先,而后再网络做为回退方案,也就是说先缓存里面找到,也总会从网络上请求资源,这种模式能够先使用缓存快速响应页面,同时会从新请求来获取最新的内容来更新缓存,在咱们用户下次请求该资源的时候,那么它就会拿到缓存里面最新的数据了,这种模式是将快速响应和最新的响应模式相结合。
所以咱们的代码改为以下:
self.addEventListener('fetch', function(event) { event.respondWith( caches.open("cache-name").then(function(cache) { return cache.match(event.request).then(function(cachedResponse) { var fetchPromise = fetch(event.request).then(function(networkResponse) { cache.put(event.request, networkResponse.clone()); return networkResponse; }); return cachedResponse || fetchPromise; }); }) ) });
如上代码,咱们首先打开一个缓存,而后咱们试图匹配请求,不论是否匹配成功,咱们都会进入then函数,在该回调函数内部,会先从新请求一下,请求成功后,把最新的内容返回回来,而且以此同时把该请求数的数据克隆一份出来放入缓存里面去。最后把请求的资源文件返回保存到 fetchPromise 该变量里面,最后咱们先返回缓存里面的数据,若是缓存里面没有数据,咱们再返回网络fetchPromise 返回的数据。
如上就是咱们3种常见的模式。下面咱们就须要来规划咱们的缓存策略了。
四:规划缓存策略
在咱们以前讲解的demo中(https://www.cnblogs.com/tugenhua0707/p/11148968.html), 都是基于网络优先,缓存做为回退方案模式的。咱们以前使用这个模式给用户体验仍是挺不错的,首先先请求网络,当网络断开的时候,咱们从缓存里面拿到数据。
这样就不会使页面异常或空白。可是上面咱们已经了解到了缓存了,咱们能够再进一步优化了。
咱们如今可使用离线优先的方式来构建咱们的应用程序了,对应咱们项目常常会改变的资源咱们优先使用网络请求,若是网络不能够用的话,咱们使用缓存里面的数据。
首先仍是看下咱们项目的整个目录结构以下:
|----- 项目 | |--- public | | |--- js # 存放全部的js | | | |--- main.js # js入口文件 | | |--- style # 存放全部的css | | | |--- main.styl # css 入口文件 | | |--- index.html # index.html 页面 | | |--- images | |--- package.json | |--- webpack.config.js | |--- node_modules | |--- sw.js
咱们的首页 index.html 代码以下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>service worker 实列</title> <link rel="stylesheet" href="/main.css" /> </head> <body> <div id="app">22222</div> <img src="/public/images/xxx.jpg" /> <script type="text/javascript" src="/main.js"></script> </body> </html>
首页是由静态的index.html 组成的,它通常不多会随着版本的改变而改变的,它页面中会请求多个图片,请求多个css样式,和请求多个js文件。在index.html中全部的静态资源文件(图片、css、js)等在咱们的service worker安装过程当中会缓存下来的,那么这些资源文件适合的是 "缓存优先,网络做为回退方案" 模式来作。这样的话,页面加载会更快。
可是index.html呢?这个页面通常状况下不多改变,咱们通常会想到 "缓存优先,网络做为回退方案" 来考虑,可是若是该页面也改动了代码呢?咱们若是一直使用缓存的话,那么咱们就得不到最新的代码了,若是咱们想咱们的index.html拿到最新的数据,咱们不得不从新更新咱们的service worker,来获取最新的缓存文件。可是咱们从以前的知识点咱们知道,在咱们旧的service worker 释放页面的同时,新的service worker被激活以前,页面也不是最新的版本的。必需要等第二次从新刷新页面的时候才会看到最新的页面。那么咱们的index.html页面要如何作呢?
1) 若是咱们使用 "缓存优先,网络做为回退方案" 模式来提供服务的话,那么这样作的话,当咱们改变页面的时候,它就有可能不会使用最新版本的页面。
2)若是咱们使用 "网络优先,缓存做为回退方案 " 模式来作的话,这样确实能够经过请求来显示最新的页面,可是这样作也有缺点,好比咱们的index.html页面没有改过任何东西的话,也要从网络上请求,而不是从缓存里面读取,致使加载的时间会慢一点。
3) 使用 缓存优先,网络做为 回退方案,并频繁更新缓存模式。该模式老是从缓存里面读取 index.html页面,那么它的响应时间相对来讲是很是快的,而且从缓存里面读取页面后,咱们同时会请求下,而后返回最新的数据,咱们把最新的数据来更新缓存,所以咱们下一次进来页面的时候,会使用最新的数据。
所以对于咱们的index.html页面,咱们适合使用第三种方案来作。
所以对于咱们这个简单的项目来说,咱们能够总结以下:
1. 使用 "缓存优先,网络做为回退方案,并频繁更新缓存" 模式来返回index.html文件。
2. 使用 "缓存优先,网络做为回退方案" 来返回首页须要的全部静态文件。
所以咱们可使用上面两点,来实现咱们的缓存策略。
五:实现缓存策略
如今咱们来更新下咱们的 sw.js 文件,该文件来缓存咱们index.html,及在index.html使用到的全部静态资源文件。
index.html 代码改为以下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>service worker 实列</title> </head> <body> <div id="app">22222</div> <img src="/public/images/xxx.jpg" /> </body> </html>
js/main.js 代码变为以下:
// 加载css样式 require('../styles/main.styl'); if ("serviceWorker" in navigator) { navigator.serviceWorker.register('/sw.js', {scope: '/'}).then(function(registration) { console.log("Service Worker registered with scope: ", registration.scope); }).catch(function(err) { console.log("Service Worker registered failed:", err); }); }
sw.js 代码变成以下:
var CACHE_NAME = "cacheName"; var CACHE_URLS = [ "/public/index.html", // html文件 "/main.css", // css 样式表 "/public/images/xxx.jpg", // 图片 "/main.js" // js 文件 ]; // 监听 install 事件,把全部的资源文件缓存起来 self.addEventListener("install", function(event) { event.waitUntil( caches.open(CACHE_NAME).then(function(cache) { return cache.addAll(CACHE_URLS); }) ) }); // 监听fetch事件,监听全部的请求 self.addEventListener("fetch", function(event) { var requestURL = new URL(event.request.url); console.log(requestURL); if (requestURL.pathname === '/' || requestURL.pathname === "/index.html") { event.respondWith( caches.open(CACHE_NAME).then(function(cache) { return cache.match("/index.html").then(function(cachedResponse) { var fetchPromise = fetch("/index.html").then(function(networkResponse) { cache.put("/index.html", networkResponse.clone()); return networkResponse; }); return cachedResponse || fetchPromise; }) }) ) } else if (CACHE_URLS.includes(requestURL.href) || CACHE_URLS.includes(requestURL.pathname)) { event.respondWith( caches.open(CACHE_NAME).then(function(cache) { return cache.match(event.request).then(function(response) { return response || fetch(event.request); }); }) ) } }); self.addEventListener("activate", function(e) { e.waitUntil( caches.keys().then(function(cacheNames) { return Promise.all( cacheNames.map(function(cacheName) { if (CACHE_NAME !== cacheName && cacheName.startWith("cacheName")) { return caches.delete(cacheName); } }) ) }) ) });
如上代码中的fetch事件,var requestURL = new URL(event.request.url);console.log(requestURL); 打印信息以下所示:
如上咱们使用了 new URL(event.request.url) 来决定如何处理不一样的请求。且能够获取到不一样的属性,好比host, hostname, href, origin 等这样的信息到。
如上咱们监听 fetch 事件中全部的请求,判断 requestURL.pathname 是不是 "/" 或 "/index.html", 若是是index.html 页面的话,对于 index.html 的来讲,使用上面的原则是:使用 "缓存优先,网络做为回退方案,并频繁更新缓存", 因此如上代码,咱们首先打开咱们的缓存,而后使用缓存匹配 "/index.html",无论匹配是否成功,都会进入then回调函数,而后把缓存返回,在该函数内部,咱们会从新请求,把请求最新的内容保存到缓存里面去,也就是说更新咱们的缓存。当咱们第二次访问的时候,使用的是最新缓存的内容。
若是咱们请求的资源文件不是 index.html 的话,咱们接着会判断下,CACHE_URLS 中是否包含了该资源文件,若是包含的话,咱们就从缓存里面去匹配,若是缓存没有匹配到的话,咱们会从新请求网络,也就是说咱们对于页面上全部静态资源文件话,使用 "缓存优先,网络做为回退方案" 来返回首页须要的全部静态文件。
所以咱们如今再来访问咱们的页面的话,以下所示:
如上所示,咱们能够看到,咱们第一次请求的时候,加载index.html 及 其余的资源文件,咱们能够从上图能够看到 加载时间的毫秒数,虽然从缓存里面读取第一次数据后,可是因为咱们的index.html 老是会请求下,把最新的资源再返回回来,而后更新缓存,所以咱们能够看到咱们第二次加载index.html 及 全部的service worker中的资源文件,能够看到第二次的加载时间更快,而且当咱们修改咱们的index.html 后,咱们刷新下页面后,第一次仍是从缓存里面读取最新的数据,当咱们第二次刷新的时候,页面才会显示咱们刚刚修改的index.html页面的最新页面了。所以就验证了咱们以前对于index.html 处理的逻辑。
使用 缓存优先,网络做为 回退方案,并频繁更新缓存模式。该模式老是从缓存里面读取 index.html页面,那么它的响应时间相对来讲是很是快的,而且从缓存里面读取页面后,咱们同时会请求下,而后返回最新的数据,咱们把最新的数据来更新缓存,所以咱们下一次进来页面的时候,会使用最新的数据。
github简单的demo