對於PWA
,在通過屢次被面試官進行靈魂拷問後😭,我對他產生了濃厚的興趣,苦於前段時間筆者忙着面試沒得時間,因而就耽擱到了如今😂。不過對於學習新技術而言,咱們老是須要去懷着一顆敬畏之心去研究的,一項技術的興起老是有着它的意義所在,也必然表明了某種趨勢。說了這麼多,那PWA
究竟是什麼呢?javascript
PWA(Progressive web apps,漸進式 Web 應用)運用現代的 Web API 以及傳統的漸進式加強策略來建立跨平臺 Web 應用程序。這些應用無處不在、功能豐富,使其具備與原生應用相同的用戶體驗優點css
MDN上的解釋老是很官方的,從字面上來講,咱們能夠知道他是一種漸進式的Web
應用,那麼何謂漸進式呢?其實就是表明着若是瀏覽器不支持,那麼對原有應用不會產生影響,對於支持該項技術的瀏覽器,他會在原有基礎上新增它的特性,讓用戶獲得更好的體驗。目前在Vue
、React
腳手架中已經集成了該項技術,一旦你擁有一個web app
項目,那麼你的PWA
之旅就已經開始了。html
爲何它會這麼火?這就不得不提到它的三大特性了:java
對於一個網站來講,怎麼留住用戶就成了咱們必須考慮的一個問題,而對於Web
應用而言,被用戶記住的一個比較粗糙的方式莫過於書籤了,可就用戶體驗層次來講,這就沒法與原生應用進行媲美了。對於一個比較大型的項目來講,開發一個原生應用的成本無疑是巨大的。web
因而咱們怎麼讓一個Web
應用具有像原生App
同樣的桌面添加直接可訪問並具備打開網站的過分效果就成了一種迫切的開發須要,PWA
應勢而生。面試
在
PWA
中有一個必須注意的點,它只支持在https
協議和localhost
即本地環境下進行使用,也就是你的應用須要被訪問必須具有這個條件。數據庫
其實對於這個功能而言,它的核心在於一個名叫manifest.json
的文件,一旦咱們的應用引入了該項配置,它就能被安裝到桌面進行使用。編程
{
"name": "HackerWeb",//應用名稱
"short_name": "HackerWeb",//短名稱,用於在桌面顯示
"start_url": ".",//入口url
"display": "standalone",//應用的展示模式,通常來講這個模式體驗最優
"background_color": "#fff",//應用的主題顏色,通常會改變你的上方菜單欄背景顏色
"description": "A simply readable Hacker News app.",//應用描述
"icons": [{//在不一樣環境下展示的應用圖標
"src": "images/touch/homescreen48.png",
"sizes": "144x144",
"type": "image/png"
}]
}
複製代碼
具體配置的詳情描述能夠參照:Web App Manifestjson
配置好以後咱們只需使用link
標籤進行引入就足夠了api
<link rel="manifest" href="manifest.json">
複製代碼
這樣你的應用就已經具有了被安裝到桌面的能力,是否是很簡單😏。
這個描述功能的實現,筆者就開始要準備放大招了🐤,它的一個核心概念能夠用一張圖來描述:
其實這項技術的實現就須要藉助咱們的ServiceWorker
以及這一個Cache Storage
來進行配合實現了。
功能的實現思路就在於ServiceWorker
能夠攔截全部請求,並能夠操做Cache Storage
進行存取操做,若是用戶斷網,咱們就能夠選擇從緩存中讀取須要的數據,這樣咱們就能實現離線緩存功能了🤒。
具體什麼是webWoker
,本文就再也不贅述了,詳細概念能夠參見阮一峯老師這篇博客,Web Worker 使用教程
想要使用它,咱們通常會在用戶首次訪問網站的時候進行註冊。爲了避免影響頁面正常的解析和頁面資源的下載,咱們會選擇在onload
事件觸發時進行ServiceWorker
的註冊,它的註冊很簡單,只須要調用一個Api
便可:
window.onload = function() {
if (navigator.serviceWorker) {
navigator.serviceWorker
.register("./sw.js")
.then(registration => {
console.log(registration);
})
.catch(err => {
console.log(err);
});
}
};
複製代碼
咱們首先會判斷該瀏覽器是否支持ServiceWorker
,若是支持就進行註冊,不支持就直接跳過,不會影響頁面。這個註冊方法返回的是一個Promise
對象,咱們能夠在then
方法中獲取到registration
,這個對象包含了一些註冊成功後的信息,若是失敗,咱們能夠在catch
方法中進行捕獲。
註冊完咱們的sw.js
(文件名自定義)後,咱們就能夠在sw.js
文件中來研究它的三個核心生命週期函數了。
service worker
註冊成功的時候觸發,主要用於緩存資源service worker
激活的時候觸發,主要用於刪除舊的資源通常在這個階段咱們主要會將須要離線緩存的一些頁面、資源等存入緩存中,以便在無網絡的狀況下能夠繼續訪問網站。
self.addEventListener("install", async e => {
cacheData(); //調用緩存方法
await self.skipWaiting(); //跳過等待
// e.waitUtil(self.skipWaiting()); //另外一種跳過等待方式
});
複製代碼
首先我會調用相應的緩存資源方法,而後後面的self.skipWating
方法主要就是用於若是你的sw.js
也就是被註冊的文件發生改變就會從新觸發install
生命週期函數,可是卻不會當即觸發activite
週期,它會等待上一個sw.js
銷燬後纔會激活下一個,這個時候咱們新註冊的sw.js
並無被激活,因此爲了可以讓新註冊的sw.js
能馬上生效,咱們能夠加上這麼一句進行跳過等待。
這個地方爲何會有這麼兩種寫法呢?實際上是由於
self.skipWating
返回的是一個Promise
,是異步的,爲了保證當前周期函數執行完再進入下一個因此咱們須要等待它執行完成,這裏可使用async await
來實現,也可使用內置的一個工具方法waitUtil
來實現相應功能。
下面咱們來解析一下代碼中cacheData
方法:
//緩存方法
const CHACH_NAME = "cache_v2";
async function cacheData() {
const cache = await caches.open(CHACH_NAME); //打開一個數據庫
const cacheList = [
"/",
"/index.html",
"/images/logo.png",
"/manifest.json",
"/index.css",
"/setting.js"
]; //須要緩存的清單
await cache.addAll(cacheList); //緩存起來
}
複製代碼
其實在這裏就用上了咱們另外一個須要研究的知識點cache storage
了。他其實有點相似於一個數據庫,通常想要使用數據庫,咱們就須要先打開一個數據庫,每一個數據庫都有一個本身的名字,知足了這些條件,咱們就能往cache storage
中存入數據了。
caches.open(cacheName).then(function(cache) {})
: 用於打開緩存,返回一個匹配cacheName的cache對象的promise,相似於鏈接數據庫caches.keys()
返回一個promise對象,包括全部的緩存的key(數據庫名)caches.delete(key)
根據key刪除對應的緩存(數據庫)cache.put(req, res)
把請求當成key,而且把對應的響應存儲起來cache.add(url)
根據url發起請求,而且把響應結果存儲起來cache.addAll(urls)
抓取一個url數組,而且把結果都存儲起來cache.match(req)
: 獲取req對應的response咱們須要先列出咱們須要進行緩存的清單,也就是代碼中的cacheList
,調用cache storage
中的addAll
方法就能將須要緩存的資源存入cache storage
中了😀。
在這個階段中,咱們通常會作的事情無非就一件事,把舊的資源或cache storage
刪除掉。
但因爲serviceWoker
在用戶瀏覽器中安裝激活後咱們並不能立馬就生效,通常會須要用戶在刷新頁面後的第二次訪問才能生效,因此咱們會在activate
階段中調用一個API,讓咱們可以在第一訪問就能生效,具體代碼以下:
const CHACH_NAME = "cache_v2";//在全局定義了當前數據庫名
self.addEventListener("activate", async e => {
/**查出數據庫全部庫名,清除舊版本庫 */
const keys = await caches.keys();
keys.forEach(key => {
//若是該數據庫名不是當前定義的名字就進行刪除
if (key !== CHACH_NAME) {
caches.delete(key);
}
});
/**用於馬上獲取頁面控制權,確保用戶第一次打開瀏覽器就是立馬生效*/
await self.clients.claim();
});
複製代碼
由於self.clients.claim()
返回的也是一個Promise
對象,因此咱們也須要等待其執行完成。
這個階段能夠說就是比較核心的生命週期函數了,由於前面兩個主要用於一些初始化的操做,而fetch
階段就真正實現離線緩存的中心樞紐,它會攔截全部頁面請求,由於這一特性,咱們就能在無網絡的狀況下將用戶須要請求的資源從緩存中讀取出來返回給用戶。
通常對於處理用戶請求,咱們會有多種策略,下面筆者就講兩種經常使用的:
顧名思義,就是先去網絡上請求,若是請求不到,再去緩存中讀取,具體代碼以下:
self.addEventListener("fetch", async e => {
const req = e.request;//拿到請求頭
await e.respondWith(networkFirst(req));//將用戶請求的資源響應給瀏覽器
});
//網絡優先
async function networkFirst(req) {
/**使用try.catch進行異常捕獲*/
try {
const res = await fetch(req);
return res;
} catch (error) {
const cache = await caches.open(CHACH_NAME); //打開一個數據庫
return await cache.match(req);//讀取緩存
}
}
複製代碼
首先會使用Fetch向對應網絡地址發起請求,若是請求不到資源就會拋出異常,就能被try catch
捕獲,而後進入catch
中進行緩存讀取。
先讀取緩存中數據,若是沒有再發起網絡請求。
//緩存優先
async function cachekFirst(req) {
const cache = await caches.open(CHACH_NAME); //打開一個數據庫
let res = await cache.match(req);//讀取緩存
if (res) {
return res;
} else {
res = await fetch(req);
return res;
}
}
複製代碼
具體代碼含義就很少加贅述了,能看到這一步應該對你沒什麼問題了吧😜。
拿到對應資源以後,咱們就只須要調用e.respondWith
方法就能把返回值響應給瀏覽器進行渲染了,至此咱們已經完成了一大步,最後就是怎麼進行系統通知了。
這個處理部分就不能放在sw.js
文件中了,由於咱們須要用到window
中的Notification
函數。
//先獲取通知權限
if (Notification.permission == "default") {
Notification.requestPermission();
}
if (!navigator.onLine) {
new Notification("提示", { body: "您已斷線,如今訪問的是緩存內容" });
}
複製代碼
對於這種系統級別的api
,第一步天然就是獲取用戶權限,而後才能進行下一步操做。在這裏筆者就只寫了一個通知用戶已經離線的功能。
筆者的文件目錄:
在index.html
文件中只須要用link
標籤引入manifest.json
和setting.js
,setting.js
中內容以下:
window.onload = function() {
if (this.navigator.serviceWorker) {
this.navigator.serviceWorker
.register("./sw.js")
.then(registration => {
console.log(registration);
})
.catch(err => {
console.log(err);
});
}
};
/** * 判斷用戶是否聯網,並給與通知提示 */
//先獲取通知權限
if (Notification.permission == "default") {
Notification.requestPermission();
}
if (!navigator.onLine) {
new Notification("提示", { body: "您已斷線,如今訪問的是緩存內容" });
}
複製代碼
洋洋灑灑也寫了3k多字,但願可以對你們有所幫助,同時也歡迎你們對錶述不正確的地方加以指正🧐。