前端性能优化4:PWA与Service Workers

本文目录:

  • 1.Cookie
  • 2.LocalStorage
  • 3.SessionStorage
  • 4.IndexedDB
  • 5.PWA
  • 6.实战操作

多种浏览器存储方式并存,如何选择?

1.Cookie

cookie.png

1993年cokkie的最初诞生就是为了让服务端辨别不同的http请求
因为HTTP请求无状态,所以需要cookie去维持客户端状态
过期时间 expire
cookie的生成方式

    1. http response header中的set-cookie
    1. js中可以通过document.cookie可以读写cookie
cookie分析.png

cookie会被种在domain下,httponly一列画勾则代表不能通过js进行读写
注意:被种在一级域名的cookie,其对应的二级域名访问也会自动携带
所以有些网站把静态资源都放在自家主域名的二级域名的做法是有待优化的

如果作为浏览器存储,太小了,只有4KB左右,所有cookie存储数据能力可以用localstorage替代
存储用户状态,则需要设置过期时间属性:expire
另外一个重要属性httponly,表示不支持js进行读写(为了安全)

cookie中在相关域名下面 —— cdn的流量损耗,所以普通的数据更加不适合用存储到cookie中
静态文件的请求一般都不需要任何额外信息
解决方法:cdn的域名和主站的域名要分开

2.LocalStorage

HTML5设计出来专门用于浏览器存储的
大小为5M左右
仅在客户端使用,不和服务端进行通信
接口封装较好
浏览器本地缓存方案

3.SessionStorage

会话级别的浏览器存储
大小为5M左右
仅在客户端使用,不和服务端进行通信
接口封装较好
常用于对于表单信息的维护

4.IndexedDB

  • IndexedDB 是一种低级API,用于客户端存储大量结构化数据。该API使用索引来实现对该数据的高性能搜索。虽然 Web Storage 对于存储较少量的数据很有用,但对于存储更大量的结构化数据来说,这种方法不太有用。IndexedDB提供了一个解决方案。
  • 实战:为应用创建离线版本

浏览器存储的位置查看


位置示意图.png

5.PWA

PWA (Progressive Web Apps) 是一种 Web App 新模型,并不是具体指某一种前沿的技术或者某一个单一的知识点,我们从英文缩写来看就能看出来,这是一个渐进式的 Web App,是通过一系列新的 Web 特性,配合优秀的 UI 交互设计,逐步的增强 Web App 的用户体验。
衡量一个webapp是不是PWA的重要标准:能不能在弱网甚至断网的环境下正常运行,在不同的网络环境下都是正常体验和渐进提升。

  • 可靠:在没有网络的环境中也能提供基本的页面访问,而不会出现“未连接到互联网”的页面。
  • 快速:针对网页渲染及网络数据访问有较好优化。
  • 融入(Engaging):应用可以被增加到手机桌面,并且和普通应用一样有全屏、推送等特性。
    一个可以监测webapp性能,pwa标准的chome工具:
    lighthouse (下载地址:https://lavas.baidu.com/doc-assets/lavas/vue/more/downloads/lighthouse_2.1.0_0.zip)

Service Worker
Service Worker 是一个脚本,浏览器独立于当前网页,将其在后台运行,为实现一些不依赖页面或者用户交互的特性打开了一扇大门。在未来这些特性将包括推送消息,背景后台同步, geofencing(地理围栏定位),但它将推出的第一个首要特性,就是拦截和处理网络请求的能力,包括以编程方式来管理被缓存的响应。
Service Worker可以理解为在js之外独立运行的另一个线程,使用Service Worker可以减少主线程被阻塞的可能性

2大实际应用场景:

  • 使用拦截和处理网络请求的能力,去实现一个离线应用(拦截请求=>当前无网络=>让Service Worker先使用本地数据进行页面渲染=>有网络时再去发请求更新页面)
  • 使用Service Worker在后台后台运行同时能和页面通信的能力,去实现大规模的后台数据,不让大任务阻塞主线程
Service Worker的生命.png

chrome提供的可以检查当前使用serviceworker的情况的调试工具:
chrome://serviceworker-internals/ 当前chrome已经被注册的serviceworker列表(曾经浏览的有serviceworker的网站都会在用户的浏览器环境上进行注册)
chrome://inspect/#service-workers 检查当前正在运行的Service Worker列表

servcie worker的查看位置.png

文件被service workers命中.png

6.实战操作

用js种cookie
document.cookie = "userName=hello"
document.cookie = "gender=male"
此时cookie就有了key值分别为userName和gender的两个cookie
获取cookie
一般会对原生方法进行封装

利用localstroge实现test.js文件缓存的实践:
首先进入网页的时候去获取localstorage里的test
var testJsContent = localStroge.getItem('test')
然后使用一个if...else进行判定,当testJsContent存在的时候,用eval去执行这个文件,没有的时候再通过请求去获取这个js文件,并且将其缓存到localstroge中

if (testJsContent){
    eval(testJsContent)
}else{
    ...
}

在浏览器中打印indexedDB
`console.log(window.indexedDB)
从打印结果可以看出其中有一个open方法
用open方法创建一个indexedDB存储
var request = window.indexedDB.open('testDB)
打印出request发现其中有onerror,onsuccess等方法
据此,我们封装一个创建指定indexedDB的方法

function openDB (name) {
    var request = window.indexedDB.open(name)
    request.onerror = function(e){
    
    }
    request.onsuccess= function(e){
        myDB.db = e.target.result
    }
}
var myDB = {
    name:"testDB",
    version:"1",
    db:null
}
openDB(myDB.name)

通过回调函数,可以在创建完之后再进行删除

function openDB (name,callback) {
    var request = window.indexedDB.open(name)
    request.onerror = function(e){
    
    }
    request.onsuccess= function(e){
        myDB.db = e.target.result
        callback&& callback()
    }
}
var myDB = {
    name:"testDB",
    version:"1",
    db:null
}
openDB(myDB.name,function(){
    myDB.db.close()
    window.indexedDB.deleteDatabase(myDB.db)
})

正常的数据库会以表的形式去存储数据,但是indexedDB没有,indexedDB的存储形式是objectStore(对象形式)
一个key值对应着一个对象(objectStore)
根据上面的代码,我们在callback中把删除indexedDB的代码去掉,增加往indexedDB添加数据的代码,完整代码如下:

function openDB(name, callback) {
    var request = window.indexedDB.open(name)
    request.onerror = function (e) {
    }
    request.onsuccess = function (e) {
        console.log(e)
        myDB.db = e.target.result
        console.log(myDB.db)
        callback && callback()
    }
    request.onupgradeneeded = function () {
        // 建立一个名字叫book的objectStore,key值对应的是isbn
        var store = myDB.db.createObjectStore('book', {
            keyPath: 'isbn'
        })
        // 建立一个唯一值的索引
        var titleIndex = store.createIndex('by_title', 'title', function () {
            unique: true
        })
        // 建立另外一个索引(不唯一)
        var authorIndex = store.createIndex('by_author', 'author')
        // 往名字为book的objectStore中添加纪录
        store.put({
            title: 'Quarry Memories',
            author: 'Fred',
            isbn: 123456
        })
        store.put({
            title: 'Quarry Buffaloes',
            author: 'Fred',
            isbn: 234567
        })
        store.put({
            title: 'Bedrock Nights',
            author: 'Barney',
            isbn: 345678
        })
    }
}
var myDB = {
    name: "testDB",
    version: "1",
    db: null
}
openDB(myDB.name, function () {
    // myDB.db.close()
    // window.indexedDB.deleteDatabase(myDB.db)
})
indexedDB示意图.png

Service Workers
注意:Service Workers只有在https的站点下才能生成
本地调试可以用localhost进行访问
需要在项目的入口文件应该添加一个启动的配置文件
首先需要判定一下当前浏览器支不支持Service Workers
作用域scope

if (navigator.serviceWorker) {
    navigator.serviceWorker.register('./service-worker.js', {
            scope: './'
        })
        .then(function (reg) {
            console.log(reg)
        })
        .catch(function (e) {
            console.log(e)
        })
}else{
    alert('Service Worker is not supported')
}

接下来需要写一下对应的启动文件
在Service Workers进行install的时候,我们先通过waitUntil 阻塞一下,然后调用caches开启cache storage,将相关的文件(app.js和main.css)扔进cache storage进行缓存

self.addEventListener('install',function(event){
    event.waitUntil(
        caches.open('app-v1')
        .then(function(cache){
            console.log('open cache')
            return cache.addAll([
                './app.js',
                './main.css',    
                                './service-worker.js',    
            ])
        })
    )
})

注意:service-worker.js是配置文件自身,因为scope把当前目录的所有文件的请求都拦截了,如果不把自身加入到缓存,同时又无法通过网络请求到自身,则页面会会无法运行。
接下来通过fetch进行请求的拦截和相应的伪造
通过respondwith改变fetch返回事件(请求文件命中缓存则直接从缓存中获取数据,没有命中则利用fetch发起请求从网络上获取文件)

self.addEventListener('fetch', function (event) {
    event.respondWith(
        caches.match(event.request).then(function (res) {
            if (res) {
                return res
            } else {
                // 通过fetch方法向网络发起请求
                fetch(url).then(function (res) {
                    if (res) {
                        // 对于新请求到的资源存储刀我们的cache storage中
                    } else {
                        // 发起用户请求
                    }
                })
            }
        })
    )
})
service worker实现主页面之间都得通信

在启动页面中添加下面的代码把变量value传递给service worker


image.png

这时候已经通过postmessage把信息发送出去了,接下来在配置文件中监听message事件,在事件监听的回调事件中,需要循环遍历当前浏览器所有的页面,如果页面是当前发送消息的主页面,则return,不是主页面再通过postMessage发送信息。

self.addEventListener('message', function (event) {
    var promise = self.ClientRectList.matchAll().then(function (clientList) {
        var senderID = event.source ? event.source.id : 'unknown'
        clientList.forEach(function(client){
            if(client.id == senderID){
                return
            }else{
                client.postMessage({
                    client:senderID,
                    message:event.data
                })
            }
        })
    })
    event.waitUntil(promise)
})

接下来再回到启动页面,监听message事件,把收到的信息发送给每个页面,定义一个变量用来显示拿到的信息var msgBox = document.getElementById('msg-box')

navigator.serviceWorker.addEventListener('message', function (event) {
    msgBox.innerHTML = msgBox.innerHTML + ('
  • ' + event.data.message + '
  • ') })

    你可能感兴趣的:(前端性能优化4:PWA与Service Workers)