客戶端存儲(chǔ)技術(shù)
介紹瀏覽器的客戶端存儲(chǔ)技術(shù),包括:Cookie、Storage、IndexedDB、Application Cache和已經(jīng)過(guò)時(shí)但還是很常見(jiàn)的Web SQL,以及一些其他的還未普及的存儲(chǔ)技術(shù):Cache Storage、FileSystem API。
Cookie
特點(diǎn):
- 具有大小限制,4~8k
- 綁定到域名,對(duì)同一個(gè)域名的請(qǐng)求會(huì)自動(dòng)帶上cookie請(qǐng)求頭
- 能夠給cookie設(shè)置有效時(shí)間
cookie使用key=value的形式記錄用戶信息,多個(gè)鍵值對(duì)之間使用;分隔
前端通過(guò)操縱document下的cookie屬性直接操縱cookie
document.cookie
//"irstime=1492081681236; uuid_tt_dd=242117069142"
與cookie相關(guān)的請(qǐng)求頭有兩個(gè),一個(gè)是Set-Cookie(用在響應(yīng)中,用于設(shè)置cookie和附加信息。一個(gè)Set-Cookie可以設(shè)置一對(duì)或多對(duì)鍵值對(duì)),一個(gè)是Cookie(用在請(qǐng)求時(shí),表示發(fā)送的cookie)
Set-Cookie格式
Set-Cookie: <name>=<value>[; <name>=<value>]...
[; expires=<date>][; domain=<domain_name>]
[; path=<some_path>][; secure][; httponly]
Storage
Storage分為本地存儲(chǔ)(Local Storage)和會(huì)話存儲(chǔ)(Session Storage)。兩者使 用完全相同的API,但本地存儲(chǔ)會(huì)持久存在(或者直到用戶清除),而會(huì)話存儲(chǔ)只要瀏覽器 關(guān)閉就會(huì)消失。存儲(chǔ)的鍵值只能為字符串,storage存儲(chǔ)的鍵值也是和域名相掛鉤的。
API
localStorge.setItem:設(shè)置特定鍵值;
localStorge.getItem:檢索特定的鍵值;
localStorge.removeItem:刪除鍵值及其相關(guān)聯(lián)的值;
localStorge.clear:刪除所有的鍵值(只限定于發(fā)出請(qǐng)求的特定域名)
IndexedDB
IndexedDB 數(shù)據(jù)庫(kù)使用key-value鍵值對(duì)儲(chǔ)存數(shù)據(jù),IndexedDB 是事務(wù)模式的數(shù)據(jù)庫(kù)(任何操作都發(fā)生在事務(wù)中),API 基本上是異步的(使用事件模型)。
IndexedDB是一個(gè)基于JavaScript的面向?qū)ο蟮臄?shù)據(jù)庫(kù)。
允許存儲(chǔ)和檢索用鍵索引的對(duì)象; 可以存儲(chǔ)結(jié)構(gòu)化克隆算法支持的任何對(duì)象;IndexedDB也遵循同源策略。
結(jié)構(gòu)化克隆支持的對(duì)象:RegExp、Blob、File、Filelist、ImageData、CanvasPixelArray(克隆力度將會(huì)跟原始對(duì)象相同)、可以正確復(fù)制有循環(huán)引用的對(duì)象。
不支持的對(duì)象:
- Error、Function、DOM節(jié)點(diǎn)
- 對(duì)象的某些特定參數(shù)也不會(huì)被保留
- RegExp 對(duì)象的 lastIndex 字段不會(huì)被保留
- 屬性描述符,setters 以及 getters(以及其他類似元數(shù)據(jù)的功能)同樣不會(huì)被復(fù)制。例如,如果一個(gè)對(duì)象用屬性描述符標(biāo)記為 read-only,它將會(huì)被復(fù)制為 read-write,因?yàn)檫@是默認(rèn)的情況下。
- 原形鏈上的屬性也不會(huì)被追蹤以及復(fù)制。
初始化數(shù)據(jù)庫(kù)
var localDatabase = {};
localDatabase.indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.msIndexedDB;
localDatabase.IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange;
var DB = "db_user",VERSION = 1.0;
/*打開(kāi)數(shù)據(jù)庫(kù)*/
function openDB(dbName, version, objStore, columnArr) {
var db = localDatabase.db;
try {
if (db) db.close();
version = version || 1;
// 打開(kāi)數(shù)據(jù)庫(kù)
var openRequest = localDatabase.indexedDB.open(dbName, version);
// 成功打開(kāi)數(shù)據(jù)庫(kù)
openRequest.onsuccess = function() {
localDatabase.db = openRequest.result;
};
// 創(chuàng)建或刪除存儲(chǔ)對(duì)象只能在此進(jìn)行
openRequest.onupgradeneeded = function(e) {
var db = e.currentTarget.result;
if (!db.objectStoreNames.contains(objStore)) {
//創(chuàng)建存儲(chǔ)對(duì)象并設(shè)置主鍵
var store = db.createObjectStore(objStore, {
keyPath: "id"
});
// 創(chuàng)建索引,方便根據(jù)字段進(jìn)行搜索
for (var i = 0, len = columnArr.length; i < len; i++) {
store.createIndex(columnArr[i], columnArr[i], {
unique: false
});
}
console.log("創(chuàng)建存儲(chǔ)對(duì)象成功")
}
};
openRequest.onerror = function(e){
console.error(e.message);
};
} catch (e) {
console.error(e.message);
}
}
插入和刪除數(shù)據(jù)
/*添加數(shù)據(jù)*/
var request = localDatabase.db.transaction(objStore, "readwrite").objectStore(objStore).add(obj);
request.onsuccess = function(event) {
// 添加成功!
};
/*刪除數(shù)據(jù)*/
var request = db.transaction(["customers"], "readwrite").objectStore("customers").delete("444-44-4444");
request.onsuccess = function(event) {
// 刪除數(shù)據(jù)成功!
};
使用索引獲取單個(gè)值,使用游標(biāo)獲取多個(gè)值
/*使用索引獲取單個(gè)值*/
var transaction = db.transaction(objStore),
store = transaction.objectStore(objStore),
request = store.get(key);
request.onsuccess = function(evt) {
console.log(JSON.stringify(evt.target.result));
};
/* 使用游標(biāo)獲取多個(gè)值 */
var recordArr = [];
function queryAllRecords(objStore) {
recordArr = [];
var db = localDatabase.db;
try {
var transaction = db.transaction(objStore),
store = transaction.objectStore(objStore),
request = store.openCursor();
request.onsuccess = function(evt) {
var cursor = evt.target.result,
jsonStr = "";
if (cursor) {
var records = cursor.value;
jsonStr = jsonStr + JSON.stringify(records);
console.log(jsonStr);
recordArr.push(records);
cursor.continue();
}
};
} catch (e) {
console.error(e.message);
}
}
更多IDB代碼見(jiàn):IDB_Util
Application Cache
Application Cache使用的是一套緩存列表。所謂列表,只是一個(gè)非常簡(jiǎn)單的文本文檔,其中列舉了所有應(yīng)該或不應(yīng)該通過(guò)緩存機(jī)制處理的資源條目,從而指導(dǎo)瀏覽器下載特定文件、加以保存并在必要時(shí)予以使用——而不必再向服務(wù)器發(fā)出重復(fù)請(qǐng)求。
要使用Application Cache,我們需要首先在包含有緩存對(duì)象文件的網(wǎng)站中保存一個(gè)擴(kuò)展名為.appcache的文本文件。根據(jù)所使用Web服務(wù)器的具體類型,我們可能需要為.appcache文件創(chuàng)建一個(gè)自定義MIME類型以確保它們能夠正確作用于瀏覽器并可被作為應(yīng)用程序緩存文件讀取。
例如:
CACHE MANIFEST
# 以上折行必需要寫
CACHE:
# 這部分寫需要緩存的資源文件列表
# 可以是相對(duì)路徑也可以是絕對(duì)路徑
index.html
index.css
images/logo.png
js/main.js
http://img.baidu.com/js/tangram-base-1.5.2.1.js
NETWORK:
# 可選
# 這一部分是要繞過(guò)緩存直接讀取的文件
login.php
FALLBACK:
# 可選
# 這部分寫當(dāng)訪問(wèn)緩存失敗后,備用訪問(wèn)的資源
# 每行兩個(gè)文件,第一個(gè)是訪問(wèn)源,第二個(gè)是替換文件*.html /offline.html
html文件中的設(shè)置
<html manifest="demo.cache">
</html>
apache服務(wù)器中,定義
tAddType text/cache-manifest .cache
緩存更新
更新manifest文件
瀏覽器發(fā)現(xiàn)manifest文件本身發(fā)生變化,便會(huì)根據(jù)新的manifest文件去獲取新的資源進(jìn)行緩存。
當(dāng)manifest文件列表并沒(méi)有變化的時(shí)候,我們通常通過(guò)修改manifest注釋的方式來(lái)改變文件,從而實(shí)現(xiàn)更新。
使用js的方式
var appCache = window.applicationCache;
appCache.update(); //嘗試更新緩存
if (appCache.status == window.applicationCache.UPDATEREADY) {
appCache.swapCache(); //更新成功后,切換到新的緩存
}
Web SQL
web sql的規(guī)范工作已經(jīng)被W3C停止,按照官方的說(shuō)法是web sql后臺(tái)使用的都是SQLite,但為了實(shí)現(xiàn)規(guī)范前臺(tái)會(huì)有許多不同的實(shí)現(xiàn)。因此規(guī)范被停止。但是仍然有許多老系統(tǒng)、插件使用web sql,因此學(xué)習(xí)web sql對(duì)我們還是有著不小的好處。
Web SQL Database 規(guī)范中定義了三個(gè)核心方法:
- openDatabase:這個(gè)方法使用現(xiàn)有數(shù)據(jù)庫(kù)或新建數(shù)據(jù)庫(kù)來(lái)創(chuàng)建數(shù)據(jù)庫(kù)對(duì)象
- transaction:這個(gè)方法允許我們根據(jù)情況控制事務(wù)提交或回滾
- executeSql:這個(gè)方法用于執(zhí)行SQL 查詢
openDatabase
var db = openDatabase(dbName, dbVersion, showName, size,callback);
db.transaction(function(context) {
context.executeSql('CREATE TABLE IF NOT EXISTS testTable (id unique, name)');
context.executeSql('INSERT INTO testTable (id, name) VALUES (0, "Byron")');
context.executeSql('INSERT INTO testTable (id, name) VALUES (1, "Casper")');
context.executeSql('INSERT INTO testTable (id, name) VALUES (2, "Frank")');
context.executeSql('SELECT * FROM testTable where id=? and name=?', [2, 'Frank'], function(context, results) {
var len = results.rows.length, i;
console.log('Got ' + len + ' rows.');
for (i = 0; i < len; i++) {
console.log('id: ' + results.rows.item(i).id);
console.log('name: ' + results.rows.item(i).name);
}
}, errorCallback);
}, successCallback, errorCallback);
其他
Cache Storage
Cache Storage用來(lái)存儲(chǔ) Response 對(duì)象的。也就是說(shuō)用來(lái)對(duì) HTTP ,響應(yīng)做緩存的。CacheStorage 在瀏覽器上的引用名叫 caches ,它定義在 ServiceWorker 的規(guī)范中。CacheStorage 是多個(gè) Cache 的集合,而每個(gè) Cache 可以存儲(chǔ)多個(gè) Response 對(duì)象。CacheStorage的設(shè)計(jì)風(fēng)格也和ServiceWork一樣都基于Promise。
- match() 返回promise,resolve為response對(duì)象,如果在任意一個(gè)cache中找到與key對(duì)應(yīng)的響應(yīng)對(duì)象
- has() 返回promise對(duì)象,resolve為boolean,根據(jù)是否含有記錄,得到不同的值
- open() 返回promise,resolve為cache,如果不存在對(duì)應(yīng)的cache,則新建一個(gè)
- delete() 返回promise,resolve為boolean,如果刪除cache成功則為true
- keys() 返回promise,resolve為cache數(shù)組
使用cache storage與service worker建立離線應(yīng)用
在頁(yè)面腳本中添加如下代碼
//注冊(cè)一個(gè)serviceWorker
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('./sw-demo-cache.js');
}
在sw-demo-cache.js中添加如下代碼
// sw-demo-cache.js
var VERSION = 'v1';
// 緩存
self.addEventListener('install', function(event) {
event.waitUntil(
caches.open(VERSION).then(function(cache) {
//需要做緩存的文件
return cache.addAll([
'./start.html',
'./static/jquery.min.js',
'./static/mm1.jpg'
]);
})
);
});
// 緩存更新
self.addEventListener('activate', function(event) {
event.waitUntil(
caches.keys().then(function(cacheNames) {
return Promise.all(
cacheNames.map(function(cacheName) {
// 如果當(dāng)前版本和緩存版本不一致
if (cacheName !== VERSION) {
return caches.delete(cacheName);
}
})
);
})
);
});
// 捕獲請(qǐng)求并返回緩存數(shù)據(jù)
self.addEventListener('fetch', function(event) {
event.respondWith(caches.match(event.request).catch(function() {
return fetch(event.request);
}).then(function(response) {
caches.open(VERSION).then(function(cache) {
cache.put(event.request, response);
});
return response.clone();
}).catch(function() {
//沒(méi)法從緩存和請(qǐng)求中得到資源時(shí)的處理辦法
return caches.match('./static/mm1.jpg');
}));
});
FileSystem API
使用 FileSystem API,網(wǎng)絡(luò)應(yīng)用就可以創(chuàng)建、讀取、導(dǎo)航用戶本地文件系統(tǒng)中的沙盒部分以及向其中寫入數(shù)據(jù)。但是目前FileSystem API并不普及,支持的只有chrome瀏覽器。FileSystem API提供了文件系統(tǒng)的管理、文件/目錄的管理、文件的管理等API。剛興趣的朋友可以閱讀探索FileSystem API
參考文獻(xiàn)
IndexedDB
使用IndexedDB
application cache api
Web SQL Database
CacheStorage 緩存存儲(chǔ)
借助Service Worker和cacheStorage緩存及離線開(kāi)發(fā)