一、背景介紹
第一個Web存儲的技術叫做Cookie,它是網站的身份證。是網站為了辨別用户身份,進行session(服務端的session)跟蹤而存儲在用户本地終端上的數據,也就是説它是存在電腦硬盤上的,一個很小的txt類型的文件。Cookie每次都會跟隨http請求發送到服務端,也就是説每一個http請求都會帶上我們的cookie數據,因此它存在一個安全性的問題。
cookie本身也是有很大的侷限性的,首先它很小,主流的瀏覽器最大支持 4096 字節,除了最大字節的限制,每個網站的cookie個數(也就是每一個first每一個域)也是有限制的,一般瀏覽器是20個。除此之外,cookie還會默認跟隨所有http請求發送,即使不需要使用這個cookie來鑑別用户但是它也是會跟隨http請求發送的,這樣就會造成一個網絡資源的浪費。然後部分的瀏覽器還限制了總的cookie個數300個。
在cookie的諸多侷限性下,Web Storage應運而生。Web Storage 解決了很多問題:
比如它支持存儲大量數據,支持複雜的本地數據庫,而且也不會默認跟隨http請求。Web Storage主要是有四個:
- SessionStorage
- LocalStorage
- WebSQL
- indexedDB
二、Cookie的簡單介紹
Cookie是HTML4的一個標準,它一般不需要考慮兼容。它是網站的一個身份證,服務器可以針對不同用户,做出不同的響應。cookie存儲在用户的機器上是一個純文本,就是一個txt文件並不是一個腳本,它不能執行東西只負責記錄。瀏覽器每次請求都會帶上當前網站的cookie。
Cookie分為兩種類型,一種呢是會話cookie,也就是臨時性的cookie,退出瀏覽器或者是關閉即刪除;
另一種叫持久cookie,它會一直存在,存在的時間由特定的過期時間或者是有效期來決定。
Cookie的域 Domain決定了當前的一個cookie的權限,哪一個域可以使用這個cookie。
Cookie的路徑 Path,下面一個簡單的例子:
www.baidu.com id="123456" domain="www.baidu.com"
www.baidu.com/user id="123456" user="eric" domain="www.baidu.com" path="/user/"
www.baidu.com/search id="123456";
www.baidu.com/user/search id="123456" user="eric";
如上www.baidu.com設置了一個id等於123456,domain是www.baidu.com,然後另外一個跟第一個一樣多設置了一個user,id相同,但是多了一個user=“Eric”,它的domain設置成了www.baidu.com,path就到了user下面。這兩者設置完成之後,當我們訪問www.baidu.com/search時百度只能拿到id,因為user="Eric"是屬於user這個域下面的,也就是説在search下面是獲取不到的,但是在www.baidu.com/user/search這個時候我們就可以獲取到名叫Eric的user。Path也是一種權限的控制只是相較於域domain是低一級的。
Cookie的安全secure,如果這個屬性為TRUE,那麼網站只有在https的請求下面才會攜帶當前的cookie。
Cookie的HttpOnly這個屬性如果為TRUE,那麼就不允許JavaScript操作cookie。
因為cookie是存儲在客户端一個獨立的文件,因此服務器是無法分辨用户和攻擊者的。關於cookie的目的分為兩種:一種是跨站點腳本攻擊,一種是跨站請求偽造。
三、SessionStorage
key-value的鍵值對,是HTML5新增的一個會話存儲對象。
SessionStorage是臨時保存在同一窗口,也就是同一標籤頁的數據。如果當前標籤頁關閉了,那麼SessionStorage也就失效了。這也是SessionStorage最顯著的一個特點:單頁標籤限制。
除此之外,它還有的一些特點有:
- 同源策略,也就是在同一協議,同一主機名和同一端口下的同一tab
- 只在本地存儲,不會跟隨http請求發送到服務器
- 存儲方式採用key-value鍵值對,這裏面的value只能存字符串類型,如果存其他的會自動轉換成字符串。
- 存儲上線限制達到了5MB,如果當前存儲超出上限新的內容會把舊的內容覆蓋但不會報錯。
屬性:
- sessionStorage.length - 鍵值對數量
- sessionStorage.key(int index) -> null
- sessionStorage.getItem(string key) -> null
- sessionStorage[string key]
- sessionStorage.setItem(string key, string value)
- sessionStorage.removeItem(string key)
- sessionStorage.clear()
Json對象
- JSON.stringify()
- JSON.parse()
四、LocalStorage
LocalStorage也是在瀏覽器的Application下面有一個Local Storage,它和SessionStorage是十分相似的,同樣是key-value鍵值對,也是HTML5的新增存儲對象,它與SessionStorage的特點不同之處在於沒有標籤頁的限制和在瀏覽器的無痕模式下LocalStorage是不允許讀取的,永久性的存儲,然後SessionStorage超出限制是覆蓋不會報錯而LocalStorage超出會報錯。
特點:
- 同源策略,也就是在同一協議,同一主機名和同一端口下的同一tab
- 沒有標籤頁的限制
- 只在本地存儲,不會跟隨http請求發送到服務器
- 存儲方式採用key-value鍵值對,這裏面的value只能存字符串類型,如果存其他的會自動轉換成字符串。
- 存儲上線限制達到了5MB,如果當前存儲超出上限會報錯。
- 無痕模式下不可讀取
- 永久性存儲
屬性:
- sessionStorage.length - 鍵值對數量
- sessionStorage.key(int index) -> null
- sessionStorage.getItem(string key) -> null
- sessionStorage[string key]
- sessionStorage.setItem(string key, string value)
- sessionStorage.removeItem(string key)
- sessionStorage.clear()
注意事項:LocalStorage和SessionStorage在web view是不可靠的,web view指的是在開發混合APP的時候使用了瀏覽器來實現我們的APP,這個時候是不可靠的,因為在瀏覽器崩潰的情況下數據可能沒有存進去。
另外一個在IOS瀏覽器中不可重複setItem,如果重複會報錯,然後這個時候我們需要先removeItem再添加item。
監聽storage的變化
監聽storage包括SessionStorage和LocalStorage。然後這裏需要提到兩個概念:同源和監聽同源網頁。
-
同源:協議、域名、端口三者相同,同源的情況下我們可以共享SessionStorage和LocalStorage。
同源策略還禁止不同源執行任何腳本。http://localhost:63342/simpleApp/app/index.html#/ (協議) (域名) (端口) (路徑) -
監聽同源網頁,但是同一網頁是無效的
window.addEventListener("storage", function (event) { console.log(event.key); console.log(event.oldValue); console.log(event.newValue); console.log(event.url); console.log(event.storageArea); });
(任何一個技術的產生都是為了解決一系列的問題)
五、IndexedDB
IndexedDB 背景
- Storage(Storage指的是SessionStorage和LocalStorage)不適合存儲大量的數據
- Storage不能提供搜索功能
- Storage不能建立索引,存儲的內容也比較少
- IndexedDB擴大了web存儲的容量,可以達到250MB以上
基本概念
首先它是一個NoSQL,也就是一個非關係型數據庫。MySQL和Oracle都是關係型數據庫。意思就是説如果建立了兩個表在關係型數據庫裏面我們可以通過一個外鏈把多個表聯繫起來,但是NoSQL不行,在NoSQL裏面如果想要多個表關聯起來,我們只能手動的在關聯的表裏面添加上需要關聯的另外一個或多個表的id。這是NoSQL與MySQL兩者之間的一個區別。
IndexedDB的特點也是和Storage是一樣的:
- 鍵值對儲存 ,但是允許所有類型,不允許主鍵重複報錯
- 是一個異步操作, 不阻塞瀏覽器線程
- 支持事務,事務是SQL數據庫的一個概念,也就是説我們進行任何的增刪改查都要在某一個事務下面進行,提供了一個回滾功能,一系列操作有一步失敗, 數據庫回滾到事務發生之前的狀態,這樣為了避免操作中途出現失敗,影響整個數據庫的狀態
- 同源限制
- 支持二進制儲存
IndexedDB幾個基本概念:
- IDBDatabase - 數據庫
- IDBObjectStore - 對象倉庫
- IDBIndex - 索引
- IDBTransaction - 事務
- IDBRequest - 操作請求
- IDBCursor - 指針
- IDBKeyRange - 主鍵集合
IndexedDB瀏覽器兼容
IDBDatabase
IDB是IndexedDB的縮寫,它呢就是數據庫,數據庫也叫作數據的一個容器。每一個源(同源策略)可以建立很多數據庫。Database有一個版本的概念,版本對應着數據庫表,同一時刻只能存在一個版本。比如:新增一個表,然後我們需要把database的版本加一,表裏面要新增一列,這時同樣需要把數據庫版本加一。
注意:1 同一時刻只能有一個版本存在 2 修改數據庫結構只能通過升級數據庫版本
-
打開數據庫
/* databaseName不存在則創建 */ /* version為整數, 新建時為1 */ let database; let userStore; const request = window.indexedDB.open(databaseName, version); /* 成功打開數據庫 */ request.onsuccess = event => { database = request.result; } /* 打開數據庫失敗 */ request.onerror = error => { console.log(error); } /* 版本號大於當前數據庫版本 */ request.onupgradeneeded = event => { database = event.target.result; }
注意:如果在打開數據庫時,數據庫不存在,將會新建一個
IDBObjectStore(數據庫表)
創建表,最好是在upgradeneeded下執行;在創建數據庫表的時候需要指定主鍵,主鍵代表了唯一的標識,比如 keyPath:‘id’;如果不指定主鍵,我們可以指定一個autoIncrement:true,自增的一個概念,也就是不指定主鍵數據庫會自動添加主鍵而且這個主鍵就是數字,依次遞增的。
const createStore = () => {
//如果當前的objectStoreNames.contains包含user,如果不包含user這個表,然後就用這個database.createObjectStore創建了一個表,這個表的名字就叫做user,然後主鍵就是下面的id
if(!db.objectStoreNames.contains('user')) {
userStore = database.createObjectStore('user', { keyPath: 'id' });
}
}
指定索引:
const createStore = () => {
if(!database.objectStoreNames.contains('user')) {
userStore = database.createObjectStore('user', { keyPath: 'id' });
userStore.createIndex('name', 'name', { unique: true });
}
}
IDBTransaction(事務)
創建完之後需要往裏面添加數據,添加數據我們就需要使用到事務。
事務涉及到數據庫的增刪改查,它有三個狀態:
- complete
- error
- abort
屬性:
- IDBTransaction.db 當前數據庫
- IDBTransaction.mode 模式,使用模式分為readonly和readwrite
- IDBTransaction.objectStoreNames 當前數據庫涉及到的哪幾個數組表
- IDBTransaction.error 回調
數據庫的基本操作:增刪改查以及清空。
新增數據(add)
分為兩種情況:一種是使用自增的數據庫的id或者是自增的一個鍵值,如果已經創建主鍵,那麼新增必須包含主鍵和另一種已創建主鍵但主鍵不可重複。
const add = () => {
/* 創建事務 */
/* 使用某個數據庫 */
/* add新增 */
transactionRequest = database.transaction(['user'], 'readwrite')
.objectStore('user')
.add({ id: 100, name: 'Eric', age: 28, email: 'Ericlee00@163.com' });
/* 成功 */
transactionRequest.onsuccess = event => {
console.log('數據寫入成功', event);
};
/* 失敗 */
transactionRequest.onerror = error => {
console.log('數據寫入失敗', error);
}
}
讀取數據(get)
const read = () => {
/* 創建事務 */
transaction = database.transaction(['user']);
/* 選擇數據庫表 */
table = transaction.objectStore('user');
/* 讀取數據 */
transactionRequest = table.get(2);
/* 成功 */
transactionRequest.onerror = event => {
console.log('數據讀取失敗', event);
};
/* 失敗 */
transactionRequest.onsuccess = event => {
if (transactionRequest.result) {
console.log('數據讀取成功', transactionRequest.result);
} else {
console.log('未讀取到數據');
}
};
}
更新數據(put)
更新不存在的數據時會新建,也就是説在新增數據時如果相同,往往會出錯,但是在更新數據時不會出錯。如果數據不存在就會新建,如果存在就會一直更新。
const update = () => {
transactionRequest = database.transaction(['user'], 'readwrite')
.objectStore('user')
.put({ id: count, name: 'David', age: 35, email: 'David@xiakedao.com' });
transactionRequest.onsuccess = function (event) {
console.log('更新數據成功', event);
};
transactionRequest.onerror = error => {
console.log('更新數據失敗', error);
}
}
刪除數據(delete)
const delete = () => {
transactionRequest = database.transaction(['user'], 'readwrite')
.objectStore('user')
.delete(2);
transactionRequest.onsuccess = function (event) {
console.log('刪除數據成功', event);
};
transactionRequest.onerror = error => {
console.log('刪除數據失敗', error);
}
}
清空數據(clear)
IDBCursor(指針)
提供了一種遍歷數據的可能。
const readAll = () => {
table = database.transaction('user').objectStore('user');
table.openCursor().onsuccess = () => {
let cursor = event.target.result;
if (cursor) {
console.log('數據遍歷', cursor);
cursor.continue();
} else {
console.log('數據遍歷完成');
}
};
}
關閉IndexedDB數據庫連接
const closeDataBase = () => {
database.close();
}
刪除IndexedDB數據庫前,須先關閉數據庫連接
const deleteDataBase = () => {
indexedDB.deleteDatabase('first_database');
}
六、WebSQL
基本概念:並不是 HTML5 的規範 , 只能算是一個獨立的規範;使用WebSQL是完完全全的SQL 語句,使用SQL語句來操作客户端數據庫;它一共有三個比較重要的概念,分別是:openDatabase 打開數據庫,可以是使用現有數據庫或者新建數據庫;transaction 事務,所有的數據庫都支持事務;executeSql 執行SQL語句。
openDatabase(打開數據庫)
相比於IndexedDB的概念稍微多一點,主要是有數據庫名稱、版本號(在IndexedDB裏面版本號都是整數,但是在WebSQL裏面它可以是小數)、描述文本(介紹數據庫是幹什麼的)、數據庫大小和創建回調(function,只在第一次創建的時候才會調用)。
const database = openDatabase('my_database', '1.0', 'first', 2 * 1024 * 1024, function() {
});
Transaction(事務)
-
創建表
const createTable = () => { database.transaction(function (content) { content.executeSql('CREATE TABLE IF NOT EXISTS USER (id unique, name)'); }); } -
添加數據
const addData = () => { database.transaction(function (content) { content.executeSql('INSERT INTO USER (id, name) VALUES (1, "Eric")'); }); } -
查詢數據
const searchData = () => { database.transaction(function (content) { content.executeSql('SELECT * FROM USER'); }); } -
更新數據
const updateData = () => { database.transaction(function (content) { content.executeSql('UPDATE USER SET name=\'David\' WHERE id=1'); }); } -
刪除數據庫表
const deleteDataBase = () => { database.transaction(function (content) { content.executeSql('DROP TABLE USER'); }); }