让前端飞

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

2020-07-08  本文已影响0人  前端辉羽

本文目录:

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

1.Cookie

cookie.png

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

浏览器存储的位置查看


位置示意图.png

5.PWA

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

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

2大实际应用场景:

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 + ('<li>' + event.data.message + '</li>')
})
上一篇下一篇

猜你喜欢

热点阅读