提及前端性能优化, 咱们首先想到的可能就是用 Gulp 、Webpack 之类的自动化构建工具对 HTML、CSS 、JS 代码进行压缩,同时优化图片资源。再者就是使用 CSS Sprite 或者对于较小的图片用 base64 直接编码来进行优化。固然还有不少能够优化的方向, 例如考虑浏览器缓存、页面渲染性能 ( 减小重排与重绘和 GPU 硬件加速 ) 、JS阻塞性能等等。但咱们今天讲的是如何利用缓存策略在适宜的状况下直接减小对前端数据的请求量从而达到前端性能的优化。所以 Service Worker 以及其相关的 API 就成为了咱们今天的主角。javascript
提醒 : 本篇文章将直接讲述如何利用 Service Worker 对前端性能进行优化, 但愿读者在此以前已经对 Service Worker 有基本的了解, 若以前没有接触过, 能够先看看如下的两篇文章。
Service Worker ~ Google ( 墙 )css
首先, 既然是前端性能优化, 咱们就须要想一想该如何制定缓存策略才能达到理想的效果。咱们可能有这样的想法, 即对 CSS 、JS 等易更改文件优先使用网络请求的数据, 而对于图片资源则优先使用缓存。若是再进一步思考的话, 咱们也许会但愿在网络条件好的状况下优先使用网络请求数据, 而网络条件较差时则尽量的直接使用缓存。嗯 ~ 看起来还不错, 那么根据以上的两点咱们先用代码来实现一下吧。java
先迈出最简单的第一步, 注册 Service Worker。web
// index.js if ( 'serviceWorker' in navigator ) { navigator.serviceWorker.register('/sw.js') .then( registration => { console.log('ServiceWorker registration successful with scope: ', registration.scope); }) .catch( err => console.log('ServiceWorker registration failed: ', err)); }
在 sw.js
中实现常规操做。api
// sw.js var cacheMaps = { cache_file: 'css.js', cache_image: 'images' } self.addEventListener('install', () => { // 通常注册之后,激活须要等到再次刷新页面后再激活 // 可防止出现等待的状况,这意味着服务工做线程在安装完后当即激活 self.skipWaiting(); }) // 运行触发的事件 self.addEventListener('activate', event => { event.waitUntil( // 若缓存数据更改,则在这里更新缓存 caches.keys() .then( cacheNames => { return cacheNames.filter( item => !Object.values(cacheMaps).includes(item)) }) .then( keys => { return Promise.all( keys.map( key => { return caches.delete(key); })) }) // 更新客户端上的 Service Worker 脚本 .then(() => self.clients.claim()) ) })
实现网络优先的逻辑。浏览器
function firstNet(cacheName, request) { // 请求网络数据并缓存 return fetch(request).then( response => { var responseCopy = response.clone(); caches.open(cacheName).then( cache => { cache.put(request, responseCopy); }); return response; }).catch(() => { return caches.open(cacheName).then( cache => { return cache.match(request); }); }); }
实现缓存优先的逻辑。缓存
function firstCache(cacheName, request) { return caches.open(cacheName).then( cache => { return cache.match(request).then( response => { var fetchServer = function() { return fetch(request).then( newResponse => { cache.put(request, newResponse.clone()); return newResponse; }); } // 若是缓存中有数据则返回,不然请求网络数据 if (response) { return response; } else { return fetchServer(); } }); }); }
完成缓存策略中咱们提到的第一点,即对 CSS 、JS 请求使用网络优先,图片资源请求实现缓存优先。性能优化
// sw.js self.addEventListener('fetch', event => { var request = event.request, url = request.url, cacheName; // 网络优先 if ( /\.(js|css)$/.test(url) ) { (cacheName = cacheMaps.cache_file) && e.respondWith(firstNet(cacheName, request)); } // 缓存优先 else if ( /\.(png|jpg|jpeg|gif|webp)$/.test(url) ) { (cacheName = cacheMaps.cache_image) && e.respondWith(firstCache(cacheName, request)); } })
接下来咱们利用 Promise.race()
完成一个竞速模式, 从而实现上文提到的第二点即根据网络条件的好坏执行相应的操做。网络
function networkCacheRace(cacheName, request) { var timer, TIMEOUT = 500; /** * 网络好的状况下给网络请求500ms, 若超时则从缓存中取数据 * 若网络较差且没有缓存, 因为第一个Promise会一直处于pending, 故此时等待网络请求响应 */ return Promise.race([new Promise((resolve, reject) => { timer = setTimeout(() => { caches.open(cacheName).then( cache => { cache.match(request).then( response => { if (response) { resolve(response); } }); }); }, TIMEOUT); }), fetch(request).then( response => { clearTimeout(timer); var responseCopy = response.clone(); caches.open(cacheName).then( cache => { cache.put(request, responseCopy); }); return response; }).catch(() => { clearTimeout(timer); return caches.open(cacheName).then( cache => { return cache.match(request); }); })]); }
如今咱们能够在 sw.js
中更改一下缓存策略,从而达到最理想的效果。
// sw.js self.addEventListener('fetch', event => { // ... if ( /\.(js|css)$/.test(url) ) { (cacheName = cacheMaps.cache_file) && e.respondWith(networkCacheRace(cacheName, request)); } // ... })
什么是 Workbox ? 咱们能够看看谷歌开发者官网中给出的解释。
Workbox is a library that bakes in a set of best practices and removes the boilerplate every developer writes when working with service workers.
其大概意思是它对常见的 Service Worker 操做进行了一层封装, 根据最佳实践方便了开发者的使用。所以在咱们快速开发本身的 PWA 应用时使用 Workbox 是最合适不过的了。
它主要有如下几大功能 :
基于本文的内容, 在这里咱们只谈谈如何简单的使用 Workbox 以及它所提供的几种缓存策略。
注意在 index.js
里面的注册操做不会改变, 变化的是 sw.js
中的代码。
// sw.js // 导入谷歌提供的 Workbox 库 importScripts('https://storage.googleapis.com/workbox-cdn/releases/3.2.0/workbox-sw.js'); if ( !workbox ) { console.log(`Workbox didn't load.`); return; } // Workbox 注册成功, 能够进行下一步的操做 // 当即激活, 跳过等待 workbox.skipWaiting(); workbox.clientsClaim(); // workbox.routing.registerRoute()...
下面用官网给出的几张图解释一下 Workbox 所提供的几种缓存策略, 而它们正好能知足上文咱们本身用代码所实现的效果。
接下来让咱们使用 Workbox 去实现上文优化前端性能的缓存策略。
缓存优先 :
workbox.routing.registerRoute( /\.(png|jpg|jpeg|gif|webp)$/, // 对于图片资源使用缓存优先 workbox.strategies.cacheFirst({ cacheName: 'images', // 设置最大缓存数量以及过时时间 plugins: [ new workbox.expiration.Plugin({ maxEntries: 60, maxAgeSeconds: 7 * 24 * 60 * 60, }), ], }), );
网络优先 :
workbox.routing.registerRoute( /\.(js|css)$/, workbox.strategies.staleWhileRevalidate({ cacheName: 'css.js', }), );
由上文图中可看出 stale-while-revalidate 策略与咱们实现的网络优先稍有不一样, 确切的来讲更加明智, 由于除了第一次须要网络请求, 接下来的请求会直接从缓存中取数据但在页面加载以后会当即更新缓存, 这样既保证了加载速度又能每次将数据准确的更新到最新版本。
竞速模式 :
workbox.routing.registerRoute( /\.(js|css)$/, workbox.strategies.networkFirst({ // 给网络请求0.5秒,若仍未返回则从缓存中取数据 networkTimetoutSeconds: 0.5, cacheName: 'css.js', }), );
回头看看咱们手动实现的缓存策略, 显然使用 Workbox 要简单的多。固然 Workbox 中还有不少东西须要注意, 但因为已经超出了文章所讲的主要内容所以在这里没法具体阐述, 建议读者仍是到官网去仔细看看文档详细了解一下,若由于墙的问题能够看看第二篇文章。
Workbox ~ Google ( 墙 )