爲了提高Web應用的用戶體驗,想必不少開發者都會項目中引入離線數據存儲機制。但是面對各類各樣的離線數據技術,哪種纔是最能知足項目需求的呢?本文將幫助各位找到最合適的那一個。css
隨着HTML5的到來,各類Web離線數據技術進入了開發人員的視野。諸如AppCache、localStorage、sessionStorage和IndexedDB等等,每一種技術都有它們各自適用的範疇。好比AppCache就比較適合用於離線起動應用,或者在離線狀態下使應用的一部分功能照常運行。接下來我將會爲你們做詳細介紹,而且用一些代碼片斷來展現如何使用這些技術。html
若是你的Web應用中有一部分功能(或者整個應用)須要在脫離服務器的狀況下使用,那麼就能夠經過AppCache來讓你的用戶在離線狀態下也能使用。你所須要作的就是建立一個配置文件,在其中指定哪些資源須要被緩存,哪些不須要。此外,還能在其中指定某些聯機資源在脫機條件下的替代資源。jquery
AppCache的配置文件一般是一個以.appcache結尾的文本文件(推薦寫法)。文件以CACHE MANIFEST開頭,包含下列三部份內容:git
CACHE
– 指定了哪些資源在用戶第一次訪問站點的時候須要被下載並緩存NETWORK
– 指定了哪些資源須要在聯機條件下才能訪問,這些資源從不被緩存FALLBACK
– 指定了上述資源在脫機條件下的替代資源首先,你須要在頁面上指定AppCache的配置文件:github
<!DOCTYPE html> <html manifest="manifest.appcache"> ... </html>
在這裏千萬記得在服務器端發佈上述配置文件的時候,須要將MIME類型設置爲text/cache-manifest,不然瀏覽器沒法正常解析。數據庫
接下來是建立以前定義好的各類資源。咱們假定在這個示例中,你開發的是一個交互類站點,用戶能夠在上面聯繫別人而且發表評論。用戶在離線的狀態下依然能夠訪問網站的靜態部分,而聯繫以及發表評論的頁面則會被其它頁面替代,沒法訪問。數組
好的,咱們這就着手定義那些靜態資源:瀏覽器
CACHE MANIFEST CACHE: /about.html /portfolio.html /portfolio_gallery/image_1.jpg /portfolio_gallery/image_2.jpg /info.html /style.css /main.js /jquery.min.js
備註:配置文件寫起來有一點很不方便。舉例來講,若是你想緩存整個目錄,你不能直接在CACHE部分使用通配符(*),而是隻能在NETWORK部分使用通配符把全部不該該被緩存的資源寫出來。緩存
你不須要顯式地緩存包含配置文件的頁面,由於這個頁面會自動被緩存。接下來咱們爲聯繫和評論的頁面定義FALLBACK部分:服務器
FALLBACK: /contact.html /offline.html /comments.html /offline.html
最後咱們用一個通配符來阻止其他的資源被緩存:
NETWORK: *
最後的結果就是下面這樣:
CACHE MANIFEST CACHE: /about.html /portfolio.html /portfolio_gallery/image_1.jpg /portfolio_gallery/image_2.jpg /info.html /style.css /main.js /jquery.min.js FALLBACK: /contact.html /offline.html /comments.html /offline.html NETWORK: *
還有一件很重要的事情要記得:你的資源只會被緩存一次!也就是說,若是資源更新了,它們不會自動更新,除非你修改了配置文件。因此有一個最佳實踐是,在配置文件中增長一項版本號,每次更新資源的時候順帶更新版本號:
CACHE MANIFEST # version 1 CACHE: ...
若是你想在Javascript代碼裏面保存些數據,那麼這兩個東西就派上用場了。前一個能夠保存數據,永遠不會過時(expire)。只要是相同的域和端口,全部的頁面中都能訪問到經過LocalStorage保存的數據。舉個簡單的例子,你能夠用它來保存用戶設置,用戶能夠把他的我的喜愛保存在當前使用的電腦上,之後打開應用的時候可以直接加載。後者也能保存數據,可是一旦關閉瀏覽器窗口(譯者注:瀏覽器窗口,window,若是是多tab瀏覽器,則此處指代tab)就失效了。並且這些數據不能在不一樣的瀏覽器窗口之間共享,即便是在不一樣的窗口中訪問同一個Web應用的其它頁面。
備註:有一點須要提醒的是,LocalStorage和SessionStorage裏面只能保存基本類型的數據,也就是字符串和數字類型。其它全部的數據能夠經過各自的toString()方法轉化後保存。若是你想保存一個對象,則須要使用JSON.stringfy方法。(若是這個對象是一個類,你能夠複寫它默認的toString()方法,這個方法會自動被調用)。
咱們不妨來看看以前的例子。在聯繫人和評論的部分,咱們能夠隨時保存用戶輸入的東西。這樣一來,即便用戶不當心關閉了瀏覽器,以前輸入的東西也不會丟失。對於jQuery來講,這個功能是小菜一碟。(注意:表單中每一個輸入字段都有id,在這裏咱們就用id來指代具體的字段)
$('#comments-input, .contact-field').on('keyup', function () { // let's check if localStorage is supported if (window.localStorage) { localStorage.setItem($(this).attr('id'), $(this).val()); } });
每次提交聯繫人和評論的表單,咱們須要清空緩存的值,咱們能夠這樣處理提交(submit)事件:
$('#comments-form, #contact-form').on('submit', function () { // get all of the fields we saved $('#comments-input, .contact-field').each(function () { // get field's id and remove it from local storage localStorage.removeItem($(this).attr('id')); }); });
最後,每次加載頁面的時候,把緩存的值填充到表單上便可:
// get all of the fields we saved $('#comments-input, .contact-field').each(function () { // get field's id and get it's value from local storage var val = localStorage.getItem($(this).attr('id')); // if the value exists, set it if (val) { $(this).val(val); } });
在我我的看來,這是最有意思的一種技術。它能夠保存大量通過索引(indexed)的數據在瀏覽器端。這樣一來,就能在客戶端保存複雜對象,大文檔等等數據。並且用戶能夠在離線狀況下訪問它們。這一特性幾乎適用於全部類型的Web應用:若是你寫的是郵件客戶端,你能夠緩存用戶的郵件,以供稍後再看;若是你寫的是相冊類應用,你能夠離線保存用戶的照片;若是你寫的是GPS導航,你能夠緩存用戶的路線……不勝枚舉。
IndexedDB是一個面向對象的數據庫。這就意味着在IndexedDB中既不存在表的概念,也沒有SQL,數據是以鍵值對的形式保存的。其中的鍵既能夠是字符串和數字等基礎類型,也能夠是日期和數組等複雜類型。這個數據庫自己構建於存儲(store,一個store相似於關係型數據中表的概念)的基礎上。數據庫中每一個值都必需要有對應的鍵。每一個鍵既能夠自動生成,也能夠在插入值的時候指定,也能夠取自於值中的某個字段。若是你決定使用值中的字段,那麼只能向其中添加Javascript對象,由於基礎數據類型不像Javascript對象那樣有自定義屬性。
在這個例子中,咱們用一個音樂專輯應用做爲示範。不過我並不打算在這裏從頭至尾展現整個應用,而是把涉及IndexedDB的部分挑出來解釋。若是你們對這個Web應用感興趣的話,文章的後面也提供了源代碼的下載。首先,讓咱們來打開數據庫並建立store:
// check if the indexedDB is supported if (!window.indexedDB) { throw 'IndexedDB is not supported!'; // of course replace that with some user-friendly notification } // variable which will hold the database connection var db; // open the database // first argument is database's name, second is it's version (I will talk about versions in a while) var request = indexedDB.open('album', 1); request.onerror = function (e) { console.log(e); }; // this will fire when the version of the database changes request.onupgradeneeded = function (e) { // e.target.result holds the connection to database db = e.target.result; // create a store to hold the data // first argument is the store's name, second is for options // here we specify the field that will serve as the key and also enable the automatic generation of keys with autoIncrement var objectStore = db.createObjectStore('cds', { keyPath: 'id', autoIncrement: true }); // create an index to search cds by title // first argument is the index's name, second is the field in the value // in the last argument we specify other options, here we only state that the index is unique, because there can be only one album with specific title objectStore.createIndex('title', 'title', { unique: true }); // create an index to search cds by band // this one is not unique, since one band can have several albums objectStore.createIndex('band', 'band', { unique: false }); };
相信上面的代碼仍是至關通俗易懂的。估計你也注意到上述代碼中打開數據庫時會傳入一個版本號,還用到了onupgradeneeded事件。當你以較新的版本打開數據庫時就會觸發這個事件。若是相應版本的數據庫尚不存在,則會觸發事件,隨後咱們就會建立所需的store。接下來咱們還建立了兩個索引,一個用於標題搜索,一個用於樂隊搜索。如今讓咱們再來看看如何增長和刪除專輯:
// adding $('#add-album').on('click', function () { // create the transaction // first argument is a list of stores that will be used, second specifies the flag // since we want to add something we need write access, so we use readwrite flag var transaction = db.transaction([ 'cds' ], 'readwrite'); transaction.onerror = function (e) { console.log(e); }; var value = { ... }; // read from DOM // add the album to the store var request = transaction.objectStore('cds').add(value); request.onsuccess = function (e) { // add the album to the UI, e.target.result is a key of the item that was added }; }); // removing $('.remove-album').on('click', function () { var transaction = db.transaction([ 'cds' ], 'readwrite'); var request = transaction.objectStore('cds').delete(/* some id got from DOM, converted to integer */); request.onsuccess = function () { // remove the album from UI } });
是否是看起來直接明瞭?這裏對數據庫全部的操做都基於事務的,只有這樣才能保證數據的一致性。如今最後要作的就是展現音樂專輯:
request.onsuccess = function (e) { if (!db) db = e.target.result; var transaction = db.transaction([ 'cds' ]); // no flag since we are only reading var store = transaction.objectStore('cds'); // open a cursor, which will get all the items from database store.openCursor().onsuccess = function (e) { var cursor = e.target.result; if (cursor) { var value = cursor.value; $('#albums-list tbody').append(' '+ value.title +''+ value.band +''+ value.genre +''+ value.year +' ‘); // move to the next item in the cursor cursor.continue(); } }; }
這也不是十分複雜。能夠看見,經過使用IndexedDB,能夠很輕鬆的保存複雜對象,也能夠經過索引來檢索想要的內容:
function getAlbumByBand(band) { var transaction = db.transaction([ 'cds' ]); var store = transaction.objectStore('cds'); var index = store.index('band'); // open a cursor to get only albums with specified band // notice the argument passed to openCursor() index.openCursor(IDBKeyRange.only(band)).onsuccess = function (e) { var cursor = e.target.result; if (cursor) { // render the album // move to the next item in the cursor cursor.continue(); } }); }
使用索引的時候和使用store同樣,也能經過遊標(cursor)來遍歷。因爲同一個索引值名下可能有好幾條數據(若是索引不是unique的話),因此這裏咱們須要用到IDBKeyRange。它能根據指定的函數對結果集進行過濾。這裏,咱們只想根據指定的樂隊進行檢索,因此咱們用到了only()函數。也能使用其它相似於lowerBound(),upperBound()和bound()等函數,它們的功能也是不言自明的。
能夠看見,在Web應用中使用離線數據並非十分複雜。但願經過閱讀這篇文章,各位可以在Web應用中加入離線數據的功能,使得大家的應用更加友好易用。你能夠在這裏下載全部的源碼,嘗試一下,或者修修改改,或者用在大家的應用中。
原文:Real-World Off-Line Data Storage
轉載自:伯樂在線 - njuyz