我的博客css
在平常開發中,cookie、Session/Local,對後兩種運用的較少。
html
cookie是客戶端的解決方案,最先是網景公司的前僱員Lou Montulli在1993年3月發明的。衆所周知,HTTP是一個無狀態的協議,客戶端發起一次請求,服務器處理來自客戶端的請求,而後給客戶端回送一條響應。在客戶端與服務器數據交換完畢後,服務器端和客戶端的鏈接就會被關閉,Web服務器幾乎沒有什麼信息能夠判斷是哪一個用戶發送的請求,也沒法記錄來訪用戶的請求序列,每次交換數據都須要創建新的鏈接,後來有了用戶,網站想要去了解用戶的需求,可是根據當時場景顯然沒法知足業務需求,cookie便孕育而出,它能夠彌補HTTP協議無狀態的部分不足。前端
咱們先來看一下在平常操做中是怎麼接收到這個cookie的。vue
開始以前,將本地cookie的值所有清除。nginx
輸入'掘金',百度監聽了focus
和input
事件,因此第一次觸發了2次請求,奇怪的是第一次沒有Set-cookie返回:git
第一次觸發的是focus(),這時候Request Header請求頭是沒有任何Cookie,查詢字段wd
沒有參數,歷史記錄Hisdata讀取的是LocalStorage裏客戶端的歷史查詢與時間戳。看第二次請求github
服務器傳回了7條Set-Cooie,客戶端拿到數據後,進行儲存web
這時候能夠看到Cookie
中有服務器返回的全部cookie以及相對應的值和其餘配置;chrome
每次客戶端第一次請求,服務器接收到請求後爲該客戶端設置cookie
,服務器端向客戶端發送Cookie
是經過HTTP
響應報文實現的,在Set-Cookie
中設置須要向客戶端發送的cookie
,瀏覽器接收到響應緩存cookie
到內存或者硬盤(視瀏覽器機制),以後每次發送請求都會攜帶上cookie
:數據庫
cookie格式以下:
接收到的cookie格式
Set-cookie: name=value [; expires=date] [; path=path] [; domain=domain] [;secure=secure]
發送的cookie格式
Cookie: name1=value1 [; name2=value2]
咱們能夠手動設置一下cookie的返回,
var http = require('http');
var fs = require('fs');
http.createServer(function(request, responed) {
responed.setHeader('status', '200 OK');
responed.setHeader('Set-Cookie', 'userStatus=true;domain=.juejin.com;path=/;max-age=1000');
responed.write('掘金');
responed.end();
}).listen(8888);
複製代碼
而後啓動服務器,訪問的時候就能看到服務器返回
Set-Cookie:userStatus=true;domain=.juejin.com;path=/;max-age=1000 複製代碼
在業務開發場景中,Cookie更多的是做爲一種標識,用來記錄用戶的行爲,而並不是用戶的身份信息,根據用戶登陸後生成特定Cookie,再次發起其餘請求的時候,服務器可以識別用戶是否可以繼續這次操做,不能則相應操做。若是將用戶的身份信息及其餘重要信息放在cookie中是十分危險的,並且對於大型流量網站來講,每個字節的消耗一年下來都是按幾十TB算的,
以谷歌爲例:google
的流量,佔到整個互聯網的40%
,2016
年全球網路流量達到1.3ZB
(1ZB
= 10^9TB
),那麼google
在2016
年的流量就是1.3ZB
*40%
,若是google
每1MB
請求減小一個字節,每一年能夠節省近500TB。
SessionStorage簡稱會話存儲,和LocalStorage本地儲存是前端如今最爲普遍也是操做最簡單的本地存儲,都遵循同源策略(域名、協議、端口),存放空間很大,通常是5M,極大的解決了以前只能用Cookie來存儲數據的容量小、存取不便、容易被清除的問題。這個功能爲客戶端提供了極大的靈活性。
並不是是隻支持IE8以上,能夠經過MDN官方的方法,變相的存儲在Cookie內,不過這樣的話有失Storage對象存在的意義了。
Session只做用於當前窗口,不能跨窗口讀取其餘窗口的SessionStorage數據庫信息,瀏覽器每次新建、關閉都是直接致使當前窗口的數據庫新建和銷燬。兼容性以下:
Local做用於當前瀏覽器,即便你開2個Chrome瀏覽器(不是2個窗口)也仍是共用一個路徑地址。永遠不會自動刪除,因此若是咱們要用LocalStorage保存敏感重要信息的時候也要注意不要放在Local裏,而是放在Session裏,關閉後進行清除,不然攻擊者能夠經過XSS攻擊進行信息竊取。兼容性以下:
二者在PC端僅僅Chrome和Firefox有些許兼容誤差,移動端兼容性相同。
PS:當瀏覽器進入隱私瀏覽模式,會建立一個新的、臨時的數據庫來存儲local storage的數據;當關閉隱私瀏覽模式時,該數據庫將被清空並丟棄。
只是API名字不一樣。
localStorage.setItem('key', 'value'); // 設置
localStorage.getItem('key'); // 獲取
localStorage.removeItem('key'); // 刪除
localStorage.clear(); //清除全部複製代碼
不過在儲存和讀取數據的時候,須要將數據進行JSON.stringify和JSON.parse,不然會強制改變數據類型
var obj = {
name:'掘金',
url:'juejin.com'
}
var arr = [1,2,3]
//錯誤方法
localStorage.setItem('object',obj); //object:"[object Object]" 沒法讀取
localStorage.setItem('array',arr); //array:"1,2,3" 變成string格式
//正確方法:Object
localStorage.setItem('object',JSON.stringify(obj));//存儲 object:"{"name":"掘金","url":"juejin.com"}"
JSON.parse(localStorage.getItem('object'));//讀取 {name: "掘金", url: "juejin.com"}
//正確方法:Array
localStorage.setItem('array',JSON.stringify(arr)); //存儲 array:"[1,2,3]"
JSON.parse(localStorage.getItem('array'));//讀取 [1,2,3]複製代碼
複製代碼
原本是用來作cookie的解決方案,適合於作小規模的簡單結構數據儲存與狀態維護,不適宜儲存敏感信息。能夠運用在全部業務場景下。
IndexedDB是HTML5規範裏新出現的瀏覽器裏內置的數據庫。跟NoSQL很像,提供了相似數據庫風格的數據存儲和使用方式。但IndexedDB裏的數據是永久保存,適合於儲存大量結構化數據,有些數據本應該存在服務器,可是經過indexedDB,能夠減輕服務器的大量負擔,實現本地讀取修改使用,以對象的形式存儲,每一個對象都有一個key值索引。
IndexedDB裏的操做都是事務性的。一種對象存儲在一個object store裏,object store就至關於關係數據庫裏的表。IndexedDB能夠有不少object store,object store裏能夠有不少對象。
首先來看兼容性
Window自帶瀏覽器也只是部分支持,window8.1及以上才支持IE11。
首先,咱們建立一個數據庫
//首先定義一個本身的版本
var my = {
name:'juejin',
version:'1',
db:null
}
//打開倉庫,沒有則建立
var request = window.indexedDB.open(my.name);
//window.indexedDB.open()的第二個參數即爲版本號。在不指定的狀況下,默認版本號爲1.
//版本號不能是一個小數,不然會被轉化成最近的整數。同時可能致使不會觸發onupgradeneeded版本更新回調
console.log(request);複製代碼
返回的是一個名字爲IDBOpenDBRequest的對象。
裏面有各個狀態的回調參數,初始化的時候都是null,須要手動去掛載自定義的回調參數,從而實現window.indexedDB.open
函數的狀態回調控制,再去控制檯Appliation的indexedDB查看
咱們已經成功添加該名稱爲juejin的db對象了,security origin表示安全起源(我是在我的博客控制檯進行建立的)
request.onerror = function(event){ //打開失敗回調
console.log(`${my.name} open indexedDB is Fail`);
}
request.onsuccess = function(event){ //打開成功回調
console.warn(`${my.name} open indexedDB is success`);
//將返回的值賦給本身控制的db版本對象,下面兩種方法都能接收到。
my.db = event.target.result|| request.result;
}
request.onupgradeneeded = function (event) {//版本變化回調參數,第一次設置版本號也會觸發
console.log('indexDB version change');
}
console.log(my.db);複製代碼
返回的是一個db對象,裏面包含後續對該db對象狀態控制的回調方法。這些方法仍然須要本身定義。
怎麼關閉db對象和刪除db對象呢?關閉和刪除是兩個概念。
//關閉db對象,以後沒法對其進行插入、刪除操做。
my.db.close();
//而刪除方法則掛載在window.indexedDB下,刪除該db倉庫
window.indexedDB.deleteDatabase(my.db);複製代碼
這裏須要注意的一點是此onclose()
方法非上面代碼調用的close()
方法,my.db.close()
調用的是__proto__原型內的方法。
知道如何創建和操做indexedDB以後,咱們對object store進行添加表的操做。上文咱們說到,indexedDB中沒有表的概念,而是object store,一個數據庫中能夠包含多個object store,object store是一個靈活的數據結構,能夠存放多種類型數據。也就是說一個object store至關於一張表,裏面存儲的每條數據和一個鍵相關聯。
咱們可使用每條記錄中的某個指定字段做爲鍵值(keyPath
),也可使用自動生成的遞增數字做爲鍵值(keyGenerator
),也能夠不指定。選擇鍵的類型不一樣,objectStore能夠存儲的數據結構也有差別。
建立object store對象只能從onupgradeneeded
版本變化回調中進行。
//建立object store對象
request.onupgradeneeded = function() {
var db = request.result;
var objectStore = db.createObjectStore("LOL", {keyPath: "isbn"});
var titleIndex = objectStore.createIndex("by_hero", "hero", {unique: true});
var authorIndex = objectStore.createIndex("by_author", "author");
objectStore.put({title: "亞索", author: "Roit", isbn: 123456});
objectStore.put({title: "提莫", author: "Roit", isbn: 234567});
objectStore.put({title: "諾手", author: "Hang", isbn: 345678});
};複製代碼
createObjectStore
方法有2個參數,第一個表示該object store表的名稱,第二個是對象,keyPath
爲存儲對象的某個屬性(做爲key值),options還有個參數:autoIncrement
表明是否自增。接下來創建索引
var titleIndex = objectStore.createIndex("by_title", "title", {unique: true});
var authorIndex = objectStore.createIndex("by_author", "author");複製代碼
第一個參數是索引的名稱,第二個參數指定了根據存儲數據的哪個屬性來構建索引,第三個options對象,其中屬性unique
的值爲true
表示不容許索引值相等。第二個索引沒有options對象,接下來咱們能夠經過put方法添加數據了。
objectStore.put({hero: "亞索", author: "Roit", isbn: 123456});
objectStore.put({hero: "提莫", author: "Roit", isbn: 234567});
objectStore.put({hero: "諾手", author: "Hang", isbn: 345678});複製代碼
總體代碼寫上
var my = {//定義控制版本
name:'juejin',
version:'1',
db:null
};
var request = window.indexedDB.open(my.name); //建立打開倉庫
request.onupgradeneeded = function() {//更新版本回調
var db = request.result;
var objectStore = db.createObjectStore("LOL", {keyPath: "isbn"});
var heroIndex = objectStore.createIndex("by_hero", "hero", {unique: true});
var authorIndex = objectStore.createIndex("by_author", "author");
objectStore.put({hero: "亞索", author: "Roit", isbn: 123456});
objectStore.put({hero: "提莫", author: "Roit", isbn: 234567});
objectStore.put({hero: "諾手", author: "Hang", isbn: 345678});
};
request.onsuccess = function() {//成功回調
my.db = event.target.result|| request.result;
console.warn(`${my.name} indexedDB is success open Version ${my.version}`);
};
request.onerror = function() {//失敗回調
console.warn(`${my.name} indexedDB is fail open Version ${my.version}`);
};
複製代碼
注意只有在運行環境下才會進行一個存儲,本地打開靜態文件是不會儲存indexedDB的,雖然能彈出juejin indexedDB is success open
。這樣咱們就成功建立了一個object store,咱們到控制檯去看下
by_hero
表示在建立索引的時候,經過createObjectStore('by_hero','hero',{unique:true})
的時候,經過key值爲hero
的對象,進行索引篩選的數據。再去by_author
看下,
同理,經過key值爲author
的,進行索引的數據。這樣就可以儲存大量結構化數據。而且擁有索引能力,這一點比Storage強。固然,api也麻煩。接來下進行事務操做。
在IndexedDB
中,使用事務來進行數據庫的操做。事務有三個模式,默認只讀
readOnly
只讀。readwrite
讀寫。versionchange
數據庫版本變化//首先要建立一個事務,
var transaction = my.db.transaction('LOL', 'readwrite');
//獲取objectStore數據
var targetObjectStore = transaction.objectStore('LOL');
//對預先設置的keyPath:isbn進行獲取
var obj = targetObjectStore.get(345678);
//若是獲取成功,執行回調
obj.onsuccess = function(e){
console.log('數據成功獲取'+e.target.result)
}
//獲取失敗obj.onerror = function(e){
console.error('獲取失敗:'+e.target.result)
}複製代碼
獲取成功,拿到isbn爲345678的數據。
第一個參數爲須要關聯的object store名稱,第二個參數爲事務模式,選擇可讀可寫,與indexedDB同樣,調用成功後也會觸發onsuccess、onerror回調方法。能夠讀取了咱們嘗試去添加
targetObjectStore.add({hero: "蓋倫", author: "Yuan", isbn: 163632});
targetObjectStore.add({hero: "德邦", author: "Dema", isbn: 131245});
targetObjectStore.add({hero: "皇子", author: "King", isbn: 435112});複製代碼
有一點要注意,添加劇複數據會更新。添加完畢後,去控制檯看下
不對啊,確定有的,刷新無數遍後,終於找到了解決辦法。這多是Chrome的一個BUG吧。
刪除數據
//獲取數據是get,刪除數據是delete
targetObjectStore.delete(345678);
複製代碼
一樣 ,須要輸入篩選數據纔會觸發刷新,不過在平常中已經足夠咱們使用。
更新數據
var obj = targetObjectStore.get(123456);
//若是獲取成功,執行回調
obj.onsuccess = function(e){
console.log('數據成功獲取'+e.target.result);
var data = e.target.result;
data.hero = '亞索踩蘑菇掛了';
//再put回去
var updata = targetObjectStore.put(data);
updata.onsuccess = function(event){
console.log('更新數據成功'+event.target.result);
}
}複製代碼
又要篩選一遍.
當你須要便利整個存儲空間中的數據時,你就須要使用到遊標。遊標使用方法以下:
var request = window.indexedDB.open('juejin');
request.onsuccess = function (event) {
var db = event.target.result;
var transaction = db.transaction('LOL', 'readwrite');
//獲取object store數據
var objectStore = transaction.objectStore('LOL');
//獲取該數據的浮標
var eachData = objectStore.openCursor();
//openCursor有2個參數(遍歷範圍,遍歷順序)
eachData.onsuccess = function (event) {
var cursor = event.target.result;
if (cursor){
console.log(cursor);
cursor.continue();
}
};
eachData.onerror = function (event) {
consoe.error('each all data fail reason:'+event.target.result);
};
}複製代碼
這樣經過openCursor
獲得的數據就相似於forEach
輸出,當表中無數據,仍會書法一次onsuccess
回調
上面提到openCursor
的兩個參數,第一個是遍歷範圍,由indexedDB的 :IDBKeyRange
的API進行實現,主要有如下幾個值
//區間向上匹配,第一個參數指定邊界值,第二個參數是否包含邊界值,默認false包含。
lowerBound('邊界值',Boolean);
var index = IDBKeyRange.lowerBound(1);//匹配key>=1
var index = IDBKeyRange.lowerBound(1,true);//匹配key>1
//單一匹配,指定參數值
only('值');
var index = IDBKeyRange.only(1);//匹配key===1;
//區間向下搜索,第一個參數指定邊界值,第二個參數是否包含邊界值,默認false包含。
upperBound('邊界值',Boolean);
var index = IDBKeyRange.upperBound(2);//匹配key<=2
var index = IDBKeyRange.upperBound(2,true);//匹配key<2
//區間搜索,第一個參數指定開始邊界值,第二個參數結束邊界值,
// 第三個指定開始邊界值是否包含邊界值,默認false包含。第四個指定結束邊界值是否包含邊界值,默認false
bound('邊界值',Boolean);
var index = IDBKeyRange.bound(1,10,true,false);//匹配key>1&&key<=10;複製代碼
openCursor第二個參數,遍歷順序,指定遊標遍歷時的順序和處理相同id(keyPath屬性指定字段)重複時的處理方法。改範圍經過特定的字符串來獲取。其中:
IDBCursor.next
,從前日後獲取全部數據(包括重複數據)IDBCursor.prev
,從後往前獲取全部數據(包括重複數據)IDBCursor.nextunique
,從前日後獲取數據(重複數據只取第一條)IDBCursor.prevunique
,從後往前獲取數據(重複數據只取第一條)咱們來試一下
var request = window.indexedDB.open('juejin');
request.onsuccess = function (event) {
var db = event.target.result;
var transaction = db.transaction('LOL', 'readwrite');
//獲取object store數據
var objectStore = transaction.objectStore('LOL');
//bound('邊界值',Boolean);匹配key>22000&&key<=400000;
var index = IDBKeyRange.bound(220000,400000,true,false);
//獲取該數據的浮標,從前日後順序索引,包括重複數據
var eachData = objectStore.openCursor(index,IDBCursor.NEXT);
eachData.onsuccess = function (event) {
var cursor = event.target.result;
console.log(cursor);
if (cursor) cursor.continue();
};
eachData.onerror = function (event) {
consoe.error('each all data fail reason:'+event.target.result);
};
}
複製代碼
搜索key值爲220000到40000萬之間的數據,搜索出一條。
好了,indexedDB基本和事務操做講的差很少了,如今說說它另外一方面:
目前爲止,所知道在IndexedDB
中,鍵值對中的key
值能夠接受如下幾種類型的值:
可是儲存數據千萬要注意的一點是,若是儲存了isbn
相同的數據,是無效操做,甚至可能引發報錯。
keyPath
可以接受的數據格式,示例中createObjectStore
時設置的{KeyPath:'isbn'}
,爲主鍵
至於value幾乎能接受全部數據格式。
IndexedDB
時,可能會出現因爲沒有權限而致使的異常(LocalStorage也會),須要進行異常處理。IndexedDB
中相關的數據。什麼是ServiceWork?serviceWork是W3C 2014年提出的草案,是一種獨立於當前頁面在後臺運行的腳本。這裏的後臺指的是瀏覽器後臺,可以讓web app擁有和native app同樣的離線程序訪問能力,讓用戶可以進行離線體驗,消息推送體驗。service worker是一段腳本,與web worker同樣,也是在後臺運行。做爲一個獨立的線程,運行環境與普通腳本不一樣,因此不能直接參與web交互行爲。native app能夠作到離線使用、消息推送、後臺自動更新,service worker的出現是正是爲了使得web app也能夠具備相似的能力。
打開了如今瀏覽器單線程的革面,隨着前端性能愈來愈強,要求愈來愈高,咱們都知道在瀏覽器中,JavaScript是單線程執行的,若是涉及到大量運算的話,頗有可能阻礙css tree的渲染,從而阻塞後續代碼的執行運算速度,ServiceWork的出現正好解決了這個問題,將一些須要大規模數據運算和獲取 資源文件在後臺進行處理,而後將結果返回到主線程,由主線程來執行渲染,這樣能夠避免主線程被巨量的邏輯和運算所阻塞。這樣的大大的提高了JavaScript線程在處理大規模運算時候的一個能力, 這也是ServiceWork自己的巨大優點,好比咱們要進行WebGBL場景下3D模型和數據的運算,一個普通的數據可能高達幾MB,若是放在主線程進行運算的話,會嚴重阻礙頁面的渲染,這個時候就很是適合ServiceWork進行後臺計算,再將結果返回到主線程進行渲染。
service worker能夠:
說了這麼多,到底跟咱們實際工做中有什麼用處呢,這裏就要介紹google 的PWD(Progressive Web Apps),它是一種Web App新模型,漸進式的web App,它依賴於Service Work,是如今沒有網絡的環境中也可以提供基本的頁面訪問,不會出現‘未鏈接到互聯網’,能夠優化網頁渲染及網絡數據訪問,而且能夠添加到手機桌面,和普通應用同樣有全屏狀態和消息推送的功能。
這是Service Work
的生命週期,首先沒有Service Work的狀況下會進行一個安裝中的狀態,返回一個promise
實例,reject的會走到Error這一步,resolve
安裝成功,當安裝完成後,進入Activated
激活狀態,在這一階段,你還能夠升級一個service worker
的版本,具體內容咱們會在後面講到。在激活以後,service worker
將接管全部在本身管轄域範圍內的頁面,可是若是一個頁面是剛剛註冊了service worker
,那麼它這一次不會被接管,到下一次加載頁面的時候,service worker
纔會生效。當service worker
接管了頁面以後,它可能有兩種狀態:要麼被終止以節省內存,要麼會處理fetch
(攔截和發出網絡請求)和message
(信息傳遞)事件,這兩個事件分別是頁面初始化的時候產生了一個網絡請求出現或者頁面上發送了一個消息。
目前有哪些頁面支持service Work
呢?
在Chrome瀏覽器地址欄輸入chrome://inspect/#service-workers
,能夠看到目前爲止你訪問過全部支持service work
的網站
你也能夠打開控制檯,到Application,點擊serviceWork這一欄,
那怎麼才能體驗到service Work呢,咱們以Vue官網爲例,首先打開https://cn.vuejs.org/,等待加載完成,如今關掉你的WiFi和全部能連上互聯網的工具。再刷新地址欄頁面
是否是感受很新奇,怎麼作到呢,繼續往下看。須要運行本地環境,各類方法自行百度,我使用的是本身購買的騰訊雲服務器,nginx多開幾個端口,SFTP自動上傳。也能夠搭建本地localhost
,切記不能夠用IP地址,ServiceWork
不支持域名爲IP的網站,作好這些咱們開始。
首先建立一個文件夾,再建立index.html
,index.css
,app.js
,servicework.js
這些文件咱們後續都要用到。
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>ServiceWork</title>
</head>
<body>
<h1>ServiceWork</h1>
</body>
<script src="./app.js"></script>
<link rel="stylesheet" href="./index.css">
</html>複製代碼
引入了一個main.css文件和一個app.js
main.css
h1{
color:red;
text-align:center;
}複製代碼
app.js
alert(1);複製代碼
測試成功彈出alert(1)後,咱們開始寫代碼。
首先要肯定是否支持ServiceWork
。
app.js
if (navigator.serviceWorker) {
//先注入註冊文件,第二個參數爲做用域,爲當前目錄
navigator.serviceWorker.register('./servicework.js', {
scope: './'
}).then(function (reg) {
console.warn('install ServiceWork success',reg)
}).catch(function (err) {
console.error(err)
})
} else {
//不支持serviceWork操做
}複製代碼
導入註冊配置文件,返回一個promise,觸發相應回調,而後再去修改servicework.js
文件,
//self是serviceWorker的實例。
console.log(self)//
給實例監聽安裝事件,成功觸發回調self.addEventListener('install', function (e) {
//ExtendableEvent.waitUntil()擴展事件的生命週期。
e.waitUntil(
//咱們經過打開名稱爲'app-v1'的緩存,將讀取到的文件儲存到cache裏
caches.open('app-v1').then(function (cache) {
console.log('caches staticFile success');
//添加cache
return cache.addAll([
'./app.js',
'./servicework.html',
'./servicework.js',
'./index.css'
]);
})
);
});
複製代碼
ExtendableEvent.waitUntil()
接受一個promise
對象,它能夠擴展時間的生命週期,延長事件的壽命從而阻止瀏覽器在事件中的異步操做完成以前終止服務工做線程。它能夠擴展時間的生命週期,延長事件的壽命從而阻止瀏覽器在事件中的異步操做完成以前終止服務工做線程。
install
的時候,它會延遲將安裝的works
視爲installing
,直到傳遞的Promise
被成功地resolve
。確保服務工做線程在全部依賴的核心cache被緩存以前都不會被安裝。
一旦 Service Worker
成功安裝,它將轉換到Activation
階段。若是之前的 Service Worker
還在服務着任何打開的頁面,則新的 Service Worker
進入 waiting
狀態。新的 Service Worker
僅在舊的 Service Worker
沒有任何頁面被加載時激活。這確保了在任什麼時候間內只有一個版本的 Service Worker 正在運行。當進行
activited
的時候,它延遲將
active work
視爲已激活的,直到傳遞的
Promise
被成功地
resolve
。確保功能時間不會被分派到
ServiceWorkerGlobalScope
對象。
成功丟到緩存裏後,就可使用fetch進行網絡攔截了。
//一樣的方法,監聽fetch事件,
self.addEventListener('fetch', function (event) {
//respondWith方法產生一個request,response。
event.respondWith(
//利用match方法對event.request所請求的文件進行查詢
caches.match(event.request).then(
function (res) {
console.log(res, event.request);
//若是cache中有該文件就返回。
if (res) {
return res
} else {
//沒有找到緩存的文件,再去經過fetch()請求資源
fetch(res.url).then(function (res) {
if (res) {
if (!res || res.status !== 200 || res.type !== 'basic') {
return res;
}
//再將請求到的數據丟到cache緩存中..
var fetchRequest = event.request.clone();
var fileClone = res.clone();
caches.open('app-v1')
.then(function (cache) {
cache.put(event.request, fileClone);
});
} else {
//沒有請求到該文件,報錯處理
console.error('file not found:' + event.reuqest + '==>' + res.url)
}
})
}
}
)
);
});複製代碼
對於前端你們確定很熟悉request
,response
表明着什麼,event.respondWith()
會根據當前控制的頁面產生一個request
,request
再去生成自定義的response
,network error
或者 Fetch
的方式resolve
。
fetch()對網絡進行攔截,代理請求,先讀取本地文件,沒有資源再去請求,很大程度的節約了網絡請求消耗。
如今咱們去試試有沒有成功!
啊哈,漂亮!這樣就實現了離線訪問,可是在實際項目中,儘可能不要緩存servicework.js文件,可能沒法及時生效,進行後續修改。咱們去控制檯看下
已經安裝好了,而且在運行中。
總體大概的流程以下
Service Work.js 的更新不只僅只是簡單的更新,爲了用戶可靠性體驗,裏面仍是有不少門道的。
install
事件被觸發waiting
狀態。注意,此時並不存在替換terminated
後,新的 ServiceWork 纔會發生做用。具體行爲就是,該網頁被關閉一段時間,或者手動的清除 service worker
。而後,新的 Service Work 就度過可 waiting
的狀態。activate
事件。整個流程圖爲:
一個版本的緩存建立好以後,咱們也能夠設置多個緩存,那怎去刪除不在白名單中的緩存呢
self.addEventListener('activate', function(event) {
//上個版本,咱們使用的是'app-v1'的緩存,因此就須要進行清除,進行'app-v2'版本的緩存儲存
var cacheWhitelist = ['app-v1'];
event.waitUntil(
caches.keys().then(function(cacheNames) {
return Promise.all(
cacheNames.map(function(cacheName) {
if (cacheWhitelist.indexOf(cacheName) === -1) {
return caches.delete(cacheName);
}
})
);
})
);
});複製代碼
若是 new service work
要當即生效呢,那就要用到skipWaiting,
在 install
階段使用 self.skipWaiting();
,由於上面說到 new Service Work 加載後會觸發 install
而後進入 waiting
狀態。那麼,咱們能夠直接在 install
階段跳過等待,直接讓 new Service Work 進行接管。
self.addEventListener('install',function(event) {
self.skipWaiting();
event.waitUntil(
// caching etc
);
});複製代碼
上面的 service work
更新都是在一開始加載的時候進行的,那麼,若是用戶須要長時間停留在你的網頁上,有沒有什麼手段,在間隔時間檢查更新呢?
有的,可使用 registration.update()
來完成。
navigator.serviceWorker.register('./ServiceWork.js').then(function(reg){
// sometime later…
reg.update();
});複製代碼
另外,若是你一旦用到了 ServiceWork.js
而且肯定路由以後,請千萬不要在更改路徑了,由於,瀏覽器判斷 ServiceWork.js
是否更新,是根據 ServiceWork.js
的路徑來的。若是你修改的 ServiceWork.js
路徑,而之前的 ServiceWork.js
還在做用,那麼你新的 ServiceWork
永遠沒法工做。除非你手動啓用 update
進行更新。
你想要一個文件更新,只須要在 ServiceWork 的 fetch
階段使用 caches 進行緩存便可。一開始咱們的 install
階段的代碼爲:
self.addEventListener('install', function(event) {
event.waitUntil(
caches.open('app-v1').then(function(cache) {
return cache.addAll([
'./app.js',
'./servicework.html',
'./index.css'
]);
})
);
});
複製代碼
self.addEventListener('install', function (event) {
var now = Date.now();
// 事先設置好須要進行更新的文件路徑
var urlsToPrefetch = [
'./index.css',
'./servicework.html'
];
event.waitUntil(
caches.open(CURRENT_CACHES.prefetch).then(function (cache) {
var cachePromises = urlsToPrefetch.map(function (urlToPrefetch) {
// 使用 url 對象進行路由拼接
var url = new URL(urlToPrefetch, location.href);
url.search += (url.search ? '&' : '?') + 'cache-bust=' + now;
// 建立 request 對象進行流量的獲取
var request = new Request(url, {
mode: 'no-cors'
});
// 手動發送請求,用來進行文件的更新
return fetch(request).then(function (response) {
if (response.status >= 400) {
// 解決請求失敗時的狀況
throw new Error('request for ' + urlToPrefetch +
' failed with status ' + response.statusText);
}
// 將成功後的 response 流,存放在 caches 套件中,完成指定文件的更新。
return cache.put(urlToPrefetch, response);
}).catch(function (error) {
console.error('Not caching ' + urlToPrefetch + ' due to ' + error);
});
});
return Promise.all(cachePromises).then(function () {
console.log('Pre-fetching complete.');
});
}).catch(function (error) {
console.error('Pre-fetching failed:', error);
})
);
});複製代碼
傳送門Github查看該段代碼。
當成功獲取到緩存以後, ServiceWork 並不會直接進行替換,他會等到用戶下一次刷新頁面事後,使用新的緩存文件。
self.addEventListener('fetch', function(event) {
event.respondWith(
caches.open('app-v1').then(function(cache) {
return cache.match(event.request).then(function(response) {
var fetchPromise = fetch(event.request).then(function(res) {
cache.put(event.request, res.clone());
return res;
})
return response || fetchPromise;
})
})
);
});複製代碼
更詳細的其餘方法運用能夠參考這篇文章.
本文只講本地緩存,以後會將地理圍欄、消息推送相關信息更新在博客,有興趣的朋友能夠收藏本文章,更具體規範的代碼內容能夠到這查看。
service work(PWA)缺點:
Uncaught (in promise) DOMException: Quota exceeded.
異常。清理後必需要重啓瀏覽器才生效。優勢:
如上文所述,有着消息推送、網絡攔截代理、後臺運算、離線緩存、地理圍欄等很實用的一些技術。
本文參考了不少大神的代碼,不喜勿噴,誠心學習請指教。