本文涉及幾個知識點:fetch、caches、indexDB 等都不會詳細介紹,僅對於其中某些點帶過javascript
serviceWorker,服務工做線程,顧名思義,只是做爲工做線程存在,不摻和到JS主線程中來,介於 瀏覽器 & 服務器中間層,可攔截指定 client 所發起的全部請求html
目前 PWA(Progress Web App) 的概念很火,大體就是讓 web 也跟 app 同樣,能夠實現添加到桌面、消息推送、離線使用等功能,如 餓了麼 在三月份左右就在H5上整了個 PWA 的頁面。而其中的關鍵點,其實就是離線使用的功能,也就是 sw 在其中的做用。因爲 sw 能夠攔截 client 的請求,也就是可以根據請求,把請求後的 response 用瀏覽器緩存 caches 緩存下來,以實現離線的使用java
說到 sw 的生命週期,就得祭奠出這張圖了web
步驟分爲如下部分:chrome
- register 這個是由 client 端發起,註冊一個 serviceWorker,這須要一個專門的 sw 處理文件
- install 註冊成功後,此時 sw 中會觸發 install 事件, 需知 sw 中都是事件觸發的方式進行的邏輯調用
- activate 安裝後要等待激活,也就是 activated 事件,只要 register 成功後就會觸發 install ,但不會當即觸發 activated,這個稍後再說
- idle 在 activated 以後就能夠開始對 client 的請求進行攔截處理,sw 發起請求用的是 fetch api
- fetch 激活之後開始對網頁中發起的請求進行攔截處理
- terminate 這一步是瀏覽器自身的判斷處理,當 sw 長時間不用以後,處於閒置狀態,瀏覽器會把該 sw 暫停,直到再次使
- update 瀏覽器會自動檢測 sw 文件的更新,當有更新時會下載並 install,但頁面中仍是老的 sw 在控制,只有當用戶新開窗口後新的 sw 才能激活控制頁面
fetch api
- 發送請求時,默認不會帶上cookie,發送請求時若想帶上cookie,得顯示設定 { credential: 'include' }
- 對於跨域的資源,把模式設置爲跨域 { mode: 'cors' },不然 response 中拿不到對應的數據
caches跨域
- 只能緩存 GET & HEAD 的請求,固然安全起見
- 以上,對於 POST 等類型請求,返回數據能夠保存在 indexDB 中
serviceWorkerpromise
- 註冊的 sw 資源文件,只能監聽該 sw 的路徑 & 以後子路徑的請求,這個怎麼理解呢:也就是若資源是 /app/sw.js ,打印出來 registration.scope === /app/ 則只能監聽 /app/ 下的資源,不能監聽其餘 path,就連 /app 的也不行 !!!這意味着什麼,意味着你在 /app 目錄下注冊的 /app/sw.js,訪問 /app 時不會生效 !
![]()
- sw 提供了參數能夠設定 scope 去設定監聽的某一路徑,那麼咱們想讓 /app 生效,得怎麼作呢,其實就是得把 sw.js 放在根目錄 / ,而後設置 { scope: '/app' } 就行了
- 在 sw 中 js 報錯,不會被 client 的監控捕獲到,所以,必需要專門對 sw 的錯誤進行處理
- 基於 a 可知:sw 註冊文件,不能放在 CDN 上,必須在當前意圖監聽的 client 的 domain 下
- Request & Response 中的 body 只能被讀取一次,究其緣由,是其中包含 bodyUsed 屬性,當使用事後,這個屬性值就會變爲 true, 不能再次讀取,解決方法是,把 Request & Response clone 下來: request.clone() || response.clone()
!(function (win) { const sw = win.navigator.serviceWorker const killSW = win.killSW || false if (!sw) { return } if (!!killSW) { sw.getRegistration('/serviceWorker').then(registration => { // 手動註銷 registration.unregister() }) } else { // 表示該 sw 監聽的是根域名下的請求 sw.register('/serviceWorker.js').then(registration => { // 註冊成功後會進入回調 console.log(registration.scope) }).catch(err => { console.error(err) }) } })(window)
第一步:監聽 install 事件,sw 基於事件驅動!瀏覽器
self.addEventListener('install', event => { console.log('installed') ... })
第二步:監聽 activate 事件,sw install 以後不會當即生效,除非新打開頁面,不然當前頁面會一直是舊的 sw 掌控,所以有必要在 activate 後再對當前頁面的緩存等進行必定的處理緩存
// 定義不一樣 path 下的 cahche name const CACHE_NAME = 'TEST1' self.addEventListener('activate', event => { console.log('activated') event.waitUntil( // 刪除舊文件 caches.keys().then(cacheNames => { return Promise.all( cacheNames.map((cacheName) => { return caches.delete(cacheName); }) ); }) ); })
瀏覽器緩存 caches 會一直保存存存存到存不動了,再去刪除某些資源,這個是瀏覽器的行爲,所以仍是建議在每次更改後去刪除一些舊的瀏覽器資源,能夠本身設定
第三步:開始監聽頁面發起的請求
sw 中用的是 fetch api 去請求相應的資源,但不表明 client 中得用 fetch ,全部頁面的請求都會轉變爲 fetch 事件被 sw 捕獲
event.respondWith 接收的是一個 promise 參數,把其結果返回到 client 中
fetch 分爲三大模塊 Header、Request、Response ,這裏並不打算詳說,能夠自行去了解
self.addEventListener('fetch', event => { let { request } = event event.respondWith( // 先從 caches 中尋找是否有匹配 caches.match(request).then(res => { if (res) { return res } // 對於 CDN 資源要更改 request 的 mode if (request.mode !== 'navigate' && request.url.indexOf(request.referrer) === -1) { request = new Request(request, { mode: 'cors' }) } // 對於不在 caches 中的資源進行請求 return fetch(request).then(fetchRes => { // 這裏只緩存成功 && 請求是 GET 方式的結果,對於 POST 等請求,可把 indexDB 給用上 if(!fetchRes || fetchRes.status !== 200 || request.method !== 'GET') { return fetchRes } let resClone = fetchRes.clone() caches.open(CACHE_NAME).then(cache => { cache.put(request, fetchRes) }) return resClone }) }) ) })
調試有幾種方法:
未完待續 ...其實尚未真正把這個用到項目中去,sw 文件的放置路徑就是個大問題,如今全部靜態文件都在 CDN 上,得單獨爲它開個 VIP,能經過 client 的 host 直接訪問到的;另外 餓了麼 以前還很開心的宣佈用上了 PWA ,可是最近不知道爲啥給下線了,懼怕!