在前幾篇文章中,咱們使用service worker一步步優化了咱們的頁面,如今咱們學習使用咱們以前的indexedDB, 來緩存咱們的ajax請求,第一次訪問頁面的時候,咱們請求ajax,當咱們繼續刷新頁面的時候,咱們從緩存裏面去讀取該json數據,想要了解indexedDB,請看這篇文章。
咱們下面的demo項目是創建在咱們第三篇文章的基礎之上再進行的,想了解以前的文章,請點擊這裏.css
咱們仍是按照咱們以前的思路來作,首先咱們先看看咱們整個項目的架構以下:html
|----- 項目 | |--- public | | |--- js # 存放全部的js | | | |--- main.js # js入口文件 | | | |--- store.js | | | |--- myAccount.js | | |--- style # 存放全部的css | | | |--- main.styl # css 入口文件 | | |--- json # 存放本地模擬數據的json文件 | | | |--- index.json | | |--- index.html # index.html 頁面 | | |--- images | |--- package.json | |--- webpack.config.js | |--- node_modules | |--- sw.js
public/js/index.json(假如後端接口返回的數據是以下數據) 代碼以下:node
{ "code": 0, "data": [ { "name": "kongzhi111", "age": 28}, { "name": "kongzhi222", "age": 29}, { "name": "kongzhi333", "age": 30} ] }
在咱們的 public/js 下新建一個 store.js 文件,該js文件的做用是使用indexedDB來緩存咱們的ajax請求數據的。而且咱們須要把該 store.js 文件存放在咱們的 sw.js 中的 CACHE_URLS 中,好比以下所示:jquery
var CACHE_URLS = [ "/public/index.html", // html文件 "/main.css", // css 樣式表 "/public/images/xxx.jpg", // 圖片 "/main.js", // js 文件 "/public/js/store.js" ];
而後咱們開始編寫咱們的 store.js 代碼,/public/js/store.js 代碼以下:webpack
import axios from 'axios'; var openDataBase = function() { if (!window.indexedDB) { return false; } // 打開或建立 store-data 數據庫 var result = window.indexedDB.open('store-data', 2); // 監聽error函數觸發 result.onerror = function(event) { console.log("DataBase error:", event.target.error); } // 監聽當前版本號被升級的時候觸發該函數 result.onupgradeneeded = function(event) { var db = event.target.result; /* 是否包含該對象倉庫名(或叫表名)。若是不包含就建立一個。 該對象中的 keyPath屬性id爲主鍵 */ if (!db.objectStoreNames.contains('store')) { db.createObjectStore("store", { keyPath: "id", autoIncrement: true }); } } return result; }; /* @param {storeName} 倉庫名或表名 @param {successCallback} 須要執行的回調函數 @param {transactionMode} 事務模式 readOnly 只讀,readwrite 可讀可寫 */ var openObjectStore = function(storeName, successCallback, transactionMode) { var db = openDataBase(); if (!db) { return false; } db.onsuccess = function(event) { var targetValue = event.target.result; /* 1. 使用 targetValue.transaction(storeName, transactionMode) 來建立事務 2. 建立事務以後,咱們使用 targetValue.transaction(storeName, transactionMode).objectStore(storeName) 這個方法,拿到 IDBObjectStore對象。 */ var objectStore = targetValue.transaction(storeName, transactionMode).objectStore(storeName); successCallback(objectStore); }; return true; }; var getStore = function (successCallback) { var datas = []; var db = openObjectStore("store", function(objectStore) { // 使用流標 objectStore.openCursor() objectStore.openCursor().onsuccess = function(event) { var cursor = event.target.result; // 若是有流標的話,就把數據放入數組datas裏面去,依次循環存入進去 if (cursor) { datas.push(cursor.value); cursor.continue(); } else { // 不然的話,若是datas有數據的話,就支持調用回調函數 if (datas.length > 0) { successCallback(datas); } else { // 若是datas數據爲空,發送一個json請求 axios.get("http://localhost:8081/public/json/index.json").then(datas => { var list = datas.data.data; // 打開數據倉庫或表名,執行對應的事務操做 openObjectStore("store", function(datasStore) { for (let i = 0; i < list.length; i++) { datasStore.add(list[i]); } successCallback(datas); }, "readwrite"); }); } } } }); if (!db) { axios.get("http://localhost:8081/public/json/index.json", successCallback); } }; window.getStore = getStore;
如上代碼,有三個函數,分別爲 openDataBase、openObjectStore、及 getStore, 那麼第一個函數 openDataBase() 會打開一個新的數據庫請求,該函數代碼內部,首先會判斷瀏覽器是否支持 window.indexedDB ,若是不支持的話,直接返回,而後接着咱們建立了一個 store-data 數據庫,而且監聽了 onerror, onupgradeneeded 事件,最後咱們建立了一個 store 倉庫名或叫表名。而且以id做爲主鍵,而且設置了 autoIncrement 爲true,自動增加。而後咱們返回了該 result。由於咱們的 onsuccess事件並無在該方法中監聽。在第二個函數 openObjectStore 中,咱們會調用 建立數據庫的這個函數,而且去監聽 onsuccess這個事件。
openObjectStore() 該函數會在對象上打開一個事務,而且在其運行函數,該方法中的第一個參數爲 倉庫名稱,第二個參數爲打開倉庫後成功的回調函數,第三個參數是可選參數,它的含義是事務的類型,有 readonly(只讀) 或 readwrite(可讀可寫),如代碼:ios
db.onsuccess = function(event) { var targetValue = event.target.result; /* 1. 使用 targetValue.transaction(storeName, transactionMode) 來建立事務 2. 建立事務以後,咱們使用 targetValue.transaction(storeName, transactionMode).objectStore(storeName) 這個方法,拿到 IDBObjectStore對象。 */ var objectStore = targetValue.transaction(storeName, transactionMode).objectStore(storeName); successCallback(objectStore); };
首先咱們使用該代碼:使用 targetValue.transaction(storeName, transactionMode) 來建立事務,而後建立事務完成後,而後咱們使用git
targetValue.transaction(storeName, transactionMode).objectStore(storeName);
這個方法,拿到 IDBObjectStore對象。而後把該對象 傳入 successCallback 函數內部,在該回調函數中,咱們可使用 objectStore 來增長數據。github
getStore(): 該函數接收一個successCallback參數,指回調函數,在代碼內部,咱們首先會建立一個事務,以下代碼:web
var db = openObjectStore("store", function(objectStore) { }
而後咱們就會建立流標,而且對全部數據進行迭代,且監聽onsuccess函數,以下代碼:ajax
var db = openObjectStore("store", function(objectStore) { // 使用流標 objectStore.openCursor() objectStore.openCursor().onsuccess = function(event) { var cursor = event.target.result; // 若是有流標的話,就把數據放入數組datas裏面去,依次循環存入進去 if (cursor) { datas.push(cursor.value); cursor.continue(); } else { // 不然的話,若是datas有數據的話,就支持調用回調函數 if (datas.length > 0) { successCallback(datas); } else { // 若是datas數據爲空,發送一個json請求 axios.get("http://localhost:8081/public/json/index.json").then(datas => { var list = datas.data.data; // 打開數據倉庫或表名,執行對應的事務操做 openObjectStore("store", function(datasStore) { for (let i = 0; i < list.length; i++) { datasStore.add(list[i]); } successCallback(datas); }, "readwrite"); }); } } } }
如上代碼,若是有流標的話,在流標每次前進到一個新的記錄時都會被調用,甚至在流標經過最後一條記錄以後也會被調用,它是經過 continue 來對內部進行循環調用,當到最後一條記錄的時候,它後面就沒有數據了,所以就會進入 else 語句內部。所以首先會判斷datas 是否有數據,若是有數據的話,就會調用 successCallback(datas); 這句代碼,把數據datas做爲參數傳回給 successCallback 回調函數,不然的話,若是datas爲空的話,咱們就會去請求咱們本地的json請求,發送ajax請求,而後把請求的數據,存入到 store倉庫名中,依次循環完成後,咱們再調用 successCallback方法,把數據datas做爲參數傳遞出去。固然在咱們的 第三個函數 getStore函數中,若是不支持window.indexedDB的話,那麼該瀏覽器的話,咱們直接去請求ajax, 如getStore最後一句代碼:
if (!db) { axios.get("http://localhost:8081/public/json/index.json", successCallback); }
最後再咱們的store.js 中,咱們會使用 window.getStore = getStore; 讓其成爲全局的。而後在咱們的 /public/js/myAccount.js 代碼以下,就能夠調用咱們的 store.js 中的 getStore方法了,以下代碼所示:
import $ from 'jquery'; $(function() { // 請求數據而且渲染數據 requestAndRenderFunc(); // 向服務器請求數據,而且渲染頁面 function requestAndRenderFunc () { getStore(renderHTMLFunc); }; function renderHTMLFunc(datas) { console.log(datas); } });
如上是全部優化後的代碼,使用indexedDB來存儲咱們的數據。所以咱們來測試下頁面,咱們首先清空下咱們瀏覽器的緩存數據,而後咱們第一次訪問下咱們的頁面,咱們能夠看到咱們的網絡上,顯示以下請求:
而後咱們在控制檯中會看到返回的數據,以下圖所示:
如上咱們第一次請求的時候,咱們能夠看到會請求ajax,而後會返回內容,如今咱們繼續刷新咱們的頁面,能夠看到以下請求,以下所示:
而後再看咱們的控制檯打印以下所示:
咱們能夠看到咱們ajax請求並無發請求,可是咱們依然能夠拿到數據,這是爲何呢?這是由於咱們使用 indexedDB緩存ajax數據到本地,所以當咱們第二次之後請求的時候,咱們拿的都是 indexedDB裏面的數據,咱們並無發ajax請求,因此使用該訪問,哪怕之後訪問咱們的頁面,即便沒有網絡的狀況下,咱們依然能夠拿到數據,而且更快加載咱們的頁面。咱們再來看下咱們的 indexedDB存儲的數據以下所示:
如上代碼咱們已經實現了使用indexedDB對數據緩存了,而且使用 indexedDB緩存裏面的數據了,可是如今有一個新的問題,而且用戶點擊一個查詢按鈕,可是查詢按鈕的條件發生改變了,所以ajax請求返回的數據也是根據頁面中查詢的條件來返回的,所以這個時候咱們就不能一直使用 indexedDB中的數據了,咱們須要從新請求頁面的數據,所以咱們須要在咱們的 store.js 添加以下代碼了:
var addToObjectStore = function(storeName, object) { openObjectStore(storeName, function(store) { store.add(object); }, "readwrite"); }; var updateInObjectStore = function(storeName, id, object) { openObjectStore(storeName, function(objectStore) { objectStore.openCursor().onsuccess = function(event) { var cursor = event.target.result; if (!cursor) { return; } if (cursor.value.id === id) { objectStore.put(object); return; } cursor.continue(); } }, "readwrite"); } window.addToObjectStore = addToObjectStore; window.updateInObjectStore = updateInObjectStore;
如上 addToObjectStore 函數接收對象存儲的名稱以及要放進存儲的新對象做爲參數,該函數咱們可使用以下方式來進行調用:
addToObjectStore("store", { id: 1 });
第二個函數 updateInObjectStore 接收對象存儲的名稱,找到與給定的id參數匹配的id對象,而且用它來更新對象,這是經過在對象存儲上打開 readwrite事務,而且使用流標進行迭代來完成的。在流標到達最後一條記錄或匹配成功以前,函數會一直迭代。若是找到匹配項,就會經過 objectStore.put(object); 來進行更新,此時函數就會經過return返回回來,由於一旦找到匹配,就不須要繼續迭代下一條記錄了。該函數能夠以下調用:
updateInObjectStore("store", 1, {"id": 1, name: 'kongzhi', age: 30 });
所以爲了演示下,咱們須要把咱們的index.html 代碼變成以下了:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>service worker 實列</title> </head> <body> <div id="app">222226666</div> <img src="/public/images/xxx.jpg" /> <div style="cursor: pointer;color:red;font-size:18px;padding:5px;border:1px solid #333;" id="submit">點擊我新增</div> <div style="cursor: pointer;color:red;font-size:18px;padding:5px;border:1px solid #333;" id="update">點擊我修改</div> </body> </html>
如上代碼,咱們新增了 id = "submit" div元素,和 id = "update" 的元素,而後須要在咱們的myAccount.js 代碼添加以下:
function updateDisplay(d) { console.log(d); }; function renderHTMLFunc(datas) { console.log(datas); } var addStore = function(id, name, age) { var obj = { id: id, name: name, age: age }; addToObjectStore("store", obj); renderHTMLFunc(obj); $.getJSON("http://localhost:8081/public/json/index.json", obj, function(data) { updateDisplay(data); }); }; $("#submit").click(function(e) { addStore(3, 'longen1', '111'); }); $("#update").click(function(e) { $.getJSON("http://localhost:8081/public/json/index.json", {id: 1}, function(data) { updateInObjectStore("store", 1, data); updateDisplay(data); }); });
如上代碼,當咱們點擊 id 爲 submit的元素的時候,咱們會調用 addStore 函數,在該函數內部會根據 id, name, age 參數來新增一條數據,而後會調用 addToObjectStore 函數,先把數據添加到本地存儲裏面去,而後在渲染頁面 調用 renderHTMLFunc 函數,最後使用 $.getJSON 請求一條數據,而後把最新的數據渲染到頁面上去。
一樣的道理,update數據的時候,咱們會發ajax請求,不管服務器端是否返回了新的數據,咱們都會調用 updateInObjectStore 這個函數來更新咱們本地的數據。這樣就實現了,若是是狀態發送改變的話,那麼本地indexedDB存儲的數據庫也會從新獲得更新。
咱們能夠在咱們的項目點擊下就能夠看到效果了。咱們點擊新增一條數據後,在咱們的 indexedDB中看到信息以下:
如上咱們這邊沒有把 data裏面的數據抽離出來,直接把一整個數據直接添加進去了,反正就是這個意思,新增的時候,從新能更新咱們的indexedDB裏面的數據,同理咱們update修改數據的時候,咱們也同樣能夠修改咱們的某一條數據的。