PWA 学习心得

57 阅读9分钟

PWA基本介绍

  • 特性:安全可靠,访问更快,响应式界面,沉浸式体验
  • 功能:手动应用配置,离线加载与缓存,消息推动与通知,数据及时更新
  • 优势:渐进式,流畅(Service Worker),可安装,原生体验,粘性
  • 应用:微博、淘宝、豆瓣和饿了么

PWA核心技术揭秘

web app manifest

  • 可安装,不需要通过应用商店进行下载
  • 可以添加到桌面,有唯一的图标和名称
  • 有启动界面
  • 隐藏浏览器相关ui
{
    "name": "test",//名称
    "short_name": "test",//简称
    "start_url": ".",//指定用户从设备启动应用程序时加载的URL。 如果以相对URL的形式给出,则基本URL将是manifest的URL。
    "icons": [
        {
            "src": "./icon.png",
            "sizes": "48x48",
            "type": "image/png"
        }
    ],//指定可在各种环境中用作应用程序图标的图像对象数组
    "background_color": "#fff",//背景颜色
    "display": "standalone",//定义开发人员对Web应用程序的首选显示模式。
    "description": "A simply demo for PWA"//描述。
}

service worker

概念

  • html5 API,用来做持久的离线缓存
  • 独立的 worker 线程,独立于当前网页进程,是一种特殊的web worker
  • 允许web应用在网络环境比较差或者离线的环境下依旧可以使用

注:由于浏览器中的JavaScript是单线程的,如果在js 中加载很多耗时间,耗资源的运算过程,则会造成性能问题,web worker是一个独立的运行环境,不能操作 dom(是指文档对象模型,通过它,可以访问html文档的所有元素) 和 bom(浏览器对象模型,它使JavaScript有能力与浏览器进行对话) 脱离主线程之外

优化前:

console.log("begin")
let sum = 0
for (var i = 0; i < 10000000; i++) {
  sum += i
}
console.log(sum)
console.log("end")

// 输出结果 begin --> 49999995000000 --> end 耗性能

优化后:

console.log("begin")
const work = new Worker('./work.js')
work.addEventListener('message', e => {
  console.log(e.data)
})
work.onmessage = (e)=>{
  console.log(e.data)
}
console.log("end")

// 输出结果 begin --> end --> {sum: 49999995000000}

work.js

let sum = 0
for (var i = 0; i < 10000000; i++) {
    sum += i
}
self.postMessage({ sum: sum })

基本介绍

  • web worker 是临时的,每次做的事情的结果还不能被持久存下来,如果下次有同样的复杂操作,还得费时间的重新来一遍
  • 一旦被install,就永远存在,除非被手动unregister
  • 用到的时候可以直接唤醒,不用的时候自动睡眠
  • 可编程拦截代理请求和返回,缓存文件,缓存的文件可以被网页进程取到(包括网络离线状态)
  • 离线内容开发者可控
  • 必须在https环境下才能工作
  • 异步实现,内部大都是通过Promise实现

使用步骤

  • 在window.onload 中注册service worker,防止与其他资源竞争
  • navigator 对象中内置了serviceWorker属性
  • service worker在老版本浏览中不支持,需要进行浏览器兼容
  • 注册service worker,返回一个promise对象
 window.onload=function(){
   if ('serviceWorker' in navigator){
     navigator.serviceWorker.register('./sw.js').then(resitration=>{
       console.log(resitration)
     }).catch(err=>{
       console.log(err)
     })
   }
 }

Service worker生命周期事件

  • install事件会在service worker注册成功的时候触发,主要用于缓存资源
  • activate事件会在service worker激活的时候触发,主要用于删除旧的资源
  • fetch事件会在发送请求的时候触发,主要用于操作缓存或者读取网络资源
  • 如果sw.js发生了改变,install事件会重新触发
  • activate事件会在install事件后触发,但是如果现在已经存在service worker了,那么就处于等待状态 直到当前service worker终止
  • 可以通过self.skipWaiting()方法跳过等待,返回一个promise对象
  • 可以通过event.waitUntil()方法扩的参数是一个promise对象,会在promise结束后才会结束当前生命周期函数,防止浏览器在异步操作之前就停止了生命周期
  • service worker激活后,会在下一次刷新页面的时候生效,可以通过self.clients.claim()立即获取控制权

demo 1

self.addEventListener('install', event => {
    console.log('install', event)
})
self.addEventListener('activate', event => {
    console.log('activate', event)
})
self.addEventListener('fetch', event => {
    console.log('fetch', event)
})
// 执行顺序 install --> activate --> fetch
// 修改service worker 以后只执行install,activate事件由于现在已经存在service worker了,那么就处于等待状态 直到当前service worker终止

demo 1 优化 --> demo2

self.addEventListener('install', event => {
    console.log('install', event)
    self.skipWaiting() // 跳过等待,返回一个promise对象
})
self.addEventListener('activate', event => {
    console.log('activate', event)
})
self.addEventListener('fetch', event => {
    console.log('fetch', event)
})
// 执行顺序 install --> activate --> fetch
// 修改service worker 后执行install --> activate
// 但是由于skipWaiting 是异步的,如果install 里执行的是循环,则会发生,循环未结束,就执行activate事件

demo 2 优化 --> demo3

self.addEventListener('install', async event => {

    console.log('install', event)
    event.waitUntil(self.skipWaiting())

})
self.addEventListener('activate', event => {
    console.log('activate', event)
    // 表示service work 激活后,立刻获取控制权
    event.waitUntil(self.clients.claim())
})
self.addEventListener('fetch', event => {
    console.log('fetch', event)
})

promise

  • Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大
  • Promise可以以链式的方式来进行异步编程,解决了回调地狱的问题
  • Promise常用的静态方法
  • Promise.resolve() 返回一个解析过带着给定值的Promise对象,如果返回值是一个promise对象,则直接返回这个Promise对象。
  • Promise.reject()静态函数Promise.reject返回一个被拒绝的Promise对象
  • Promise.all() 返回一个 Promise 实例,等所有promise对象都成功了,才会成功
  • Promise.race() 竞速,只要有一个promise对象成功或者失败了,结果就是就成功或者失败了

async/await

  • ES2017 标准引入了 async 函数,使得异步操作变得更加方便
  • async用于修饰一个函数,async function fn(){} ,await函数会返回一个promise对象
  • await只能出现在async函数中,await后面跟一个promise对象,用于获取promise对象成功的结果,如果不是promise对象,直接返回值
  • await会阻塞async函数的执行 。
  • await后面的promise如果没有成功,那么会抛出异常,需要使用try..catch语法

Fetch api

  • Fetch API 提供了一个 JavaScript接口,用于访问和操纵HTTP管道的部分,例如请求和响应。
  • 在service worker中,如果想要发送请求,无法使用XMLHttpRequest, 必须使用fetch api
  • Fetch api是基于promise实现的
  • fetch(url, config) 用于发送http请求,返回一个包含响应结果的promise对象
  • response是一个二级制数据流,需要调用json()方法可以转换成json数据
  • Config常见参数
  • body:用于设置请求体
  • headers:用于设置请求头
  • method: 用于设置请求方式

拓展:Fetch API 了解 及对比ajax、axois

Cache storage

  • cacheStorage接口表示 Cache 对象的存储。配合service worker来实现资源的缓存
  • caches api类似于数据库的操作
  • caches.open(cacheName).then(function(cache) {}): 用于打开缓存,返回一个匹配cacheName的cache对象的promise,类似于连接数据库
  • caches.keys() 返回一个promise对象,包括所有的缓存的key(数据库名)
  • caches.delete(key) 根据key删除对应的缓存(数据库)
  • cache对象常用方法(单条数据的操作)
  • cache 接口为缓存的 Request / Response 对象对提供存储机制
  • cache.put(req, res) 把请求当成key,并且把对应的响应存储起来
  • cache.add(url) 根据url发起请求,并且把响应结果存储起来
  • cache.addAll(urls) 抓取一个url数组,并且把结果都存储起来
  • cache.match(req) : 获取req对应的response

常见的缓存策略

核心

利用浏览器service-worker另启一个线程,这个线程负责去监听所有https请求(注意是https),当发现某些资源是需要缓存下来的他会把资源拉取到浏览器本地,访问的时候拦截请求,不走网络请求,直接读取本地资源。

pwa 缓存提速效果

缓存方式

1、只会去缓存里拿数据,缓存没有就失败了。(适合于:频繁更新最新版本并非必需的资源,html,头像。)

2、 只请求线上,不读写缓存。(这种非常简单应用场景可能就是一万年不变的静态页面可能比较适合。)

3、Cache-First策略会在有缓存的时候返回缓存,没有缓存才会去请求并且把请求结果缓存。也就是说,第一次页面加载跟普通页面加载没有任何区别的,第二次访问的资源是直接走了本地缓存数据的。(适用于:css,js,背景图片,这种实时变化频率比较低的静态资源,可以配置缓存时间定时更新)

4、network-first 是一个比较复杂的策略。资源优先走网络,成功以后会把资源添加到缓存里面,当发现网失败就会回退读取缓存。这里面有一个点就是,多长时间算网络请求失败?这时候就需要配置一个超时时间,如果不配置回退缓存的时间就会比较长。这个时间根据自身项目而定。(适用于:频繁更新的资源,比如天气的数据,文章,游戏排行榜的接口资源,正常情况下跟普通网页没有任何区别,当出现弱网或者断网资源响应时间比较长用户体验比价差的情况下给的一种资源回退策略,这种方式可以提高弱网环境下的用户体验。)

5、这种策略比较接近cache-first,他们的区别在于他会先走缓存,走完缓存以后它会发出请求,请求的结果会用来更新缓存,也就是说你的下一次访问的如果时间足够请求返回的话,你就能拿到最新的数据了。(适合于:频繁更新最新版本并非必需的资源,html,头像。)

Notification

  • Notifications API 的通知接口用于向用户配置和显示桌面通知
  • Notification.requestPermission()可以请求用户的授权
  • new Notification(title, options) 设置通知有关内容
// 请求用户的授权
if (Notification.permission === 'default') {
  Notification.requestPermission()
}
//上线通知
window.addEventListener('online', () => {
  new Notification('提示', { body: '上线' })
})
// 离线通知 
window.addEventListener('offline', () => {
  new Notification('提示', { body: '离线' })
})

参考文献

dom 和 bom 详解 www.cnblogs.com/clschao/art…

service worker blog.csdn.net/mevicky/art…