前端性能優化4:PWA與Service Workers

本文目錄:

  • 1.Cookie
  • 2.LocalStorage
  • 3.SessionStorage
  • 4.IndexedDB
  • 5.PWA
  • 6.實戰操作

多種瀏覽器存儲方式並存,如何選擇?

1.Cookie


1993年cokkie的最初誕生就是爲了讓服務端辨別不同的http請求
因爲HTTP請求無狀態,所以需要cookie去維持客戶端狀態
過期時間 expire
cookie的生成方式

    1. http response header中的set-cookie
    1. js中可以通過document.cookie可以讀寫cookie

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提供了一個解決方案。
  • 實戰:爲應用創建離線版本

瀏覽器存儲的位置查看


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在後臺後臺運行同時能和頁面通信的能力,去實現大規模的後臺數據,不讓大任務阻塞主線程

chrome提供的可以檢查當前使用serviceworker的情況的調試工具:
chrome://serviceworker-internals/ 當前chrome已經被註冊的serviceworker列表(曾經瀏覽的有serviceworker的網站都會在用戶的瀏覽器環境上進行註冊)
chrome://inspect/#service-workers 檢查當前正在運行的Service Worker列表


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)
})

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


這時候已經通過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>')
})
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章