## 離線應用與客戶端存儲
Web應用允許使用瀏覽器提供的API實現將數據存儲到用戶的電腦上。
**1、cookie**
cookie是指Web瀏覽器存儲的少量數據,每個Cookie的大小一般不能超過4KB。
Cookie保存以下幾方面的信息。
- Cookie的名字
- Cookie的值
- 到期時間
- 所屬域名(默認是當前域名)
- 生效的路徑(默認是當前網址)
我們可以通過特殊的格式的字符串形式來讀寫Document對象的cookie屬性。
```
document.cookie //讀取當前頁面的所有cookie
```
我們可以通過檢查navigator.cookieEnabled屬性來檢測當前cookie是否啟用。若值為true,表示cookie是啟用的。
**1.1 cookie屬性:有效期和作用域**
cookie默認的有效期很短暫,它只能持續在Web瀏覽器的會話期間,一旦用戶關閉瀏覽器,cookie保存的數據就會丟失。
注意:cookie的有效期和sessionStorage的有效期是有區別的:cookie的作用域并不是局限在瀏覽器的單個窗口中,它的有效期和整個瀏覽器進程而不是單個瀏覽器窗口的有效期一致。
cookie的作用域是通過文檔源和文檔路徑來確定的。
**1.2 保存cookie**
document.cookie屬性是可寫的,可以通過它為當前網站添加Cookie。
```
document.cookie = 'user=TG';
```
Cookie的值必須寫成key=value的形式。注意,等號兩邊不能有空格。另外,寫入Cookie的時候,必須對分號、逗號和空格進行轉義(它們都不允許作為Cookie的值),這可以用encodeURIComponent方法進行編碼,讀取時再采用decodeURIComponent方法解碼。
但是,document.cookie一次只能寫入一個Cookie,而且寫入并不是覆蓋,而是添加。
```
document.cookie = 'test1=hello';
document.cookie = 'test2=world';
document.cookie
// test1=hello; test2=world
```
如果要延長cookie的有效期,就需要設置mav-age屬性來指定cookie的有效期(單位是秒)
```
document.cookie = 'user=TG;max-age=60*60*24'; //將有效期設置為一天
```
**1.3 讀取cookie**
使用JavaScript表達式來讀取cookie屬性時,其返回的值是一個字符串,該字符串都是由一系列名/值對組成,不同名/值對之間通過“分號和空格”分開,其內容包含了所有作用在當前文檔的cookie。
一般情況下,我們會采用split()方法將cookie值中的名/值對都分離出來。當然,如果之前進行了一系列的編碼,就需要先解碼再分離。
```
function getCookie(cname)
{
var name = cname + "=";
var ca = document.cookie.split(';');
for(var i=0; i<ca.length; i++)
{
var c = ca[i].trim();
if (c.indexOf(name)==0)
{
return c.substring(name.length,c.length);
}
}
return "";
}
```
**1.4 其他屬性**
除了Cookie本身的內容,還有一些可選的屬性也是可以寫入的,它們都必須以分號開頭。
```
Set-Cookie: value[; expires=date][; domain=domain][; path=path][; secure]
```
上面的Set-Cookie字段,用分號分隔多個屬性:
**(1)value屬性**
value屬性是必需的,它是一個鍵值對,用于指定Cookie的值。
**(2)expires屬性**
expires屬性用于指定Cookie過期時間。它的格式采用Date.toUTCString()的格式。
```
expires=Thu, 01 Jan 1970 00:00:00 GMT
```
如果不設置該屬性,或者設為null,Cookie只在當前會話(session)有效,瀏覽器窗口一旦關閉,當前Session結束,該Cookie就會被刪除。
瀏覽器根據本地時間,決定Cookie是否過期,由于本地時間是不精確的,所以沒有辦法保證Cookie一定會在服務器指定的時間過期。
**(3)domain屬性**
domain屬性指定Cookie所在的域名,比如example.com或.example.com(這種寫法將對所有子域名生效)、subdomain.example.com。
如果未指定,默認為設定該Cookie的域名。所指定的域名必須是當前發送Cookie的域名的一部分,比如當前訪問的域名是example.com,就不能將其設為google.com。只有訪問的域名匹配domain屬性,Cookie才會發送到服務器。
**(4)path屬性**
path屬性用來指定路徑,必須是絕對路徑(比如/、/mydir),如果未指定,默認為請求該Cookie的網頁路徑。
只有path屬性匹配向服務器發送的路徑,Cookie才會發送。這里的匹配不是絕對匹配,而是從根路徑開始,只要path屬性匹配發送路徑的一部分,就可以發送。比如,path屬性等于/blog,則發送路徑是/blog或者/blogroll,Cookie都會發送。path屬性生效的前提是domain屬性匹配。
**(5)secure**
secure屬性用來指定Cookie只能在加密協議HTTPS下發送到服務器。
該屬性只是一個開關,不需要指定值。如果通信是HTTPS協議,該開關自動打開。
**(6)max-age**
max-age屬性用來指定Cookie有效期,比如60 * 60 * 24 * 365(即一年31536e3秒)。
**2.5 cookie的限制**
瀏覽器對Cookie數量的限制,規定不一樣。目前,Firefox是每個域名最多設置50個Cookie,而Safari和Chrome沒有域名數量的限制。
所有Cookie的累加長度限制為4KB。超過這個長度的Cookie,將被忽略,不會被設置。
由于Cookie可能存在數量限制,有時為了規避限制,可以將cookie設置成下面的形式。
```
name=a=b&c=d&e=f&g=h
```
上面代碼實際上是設置了一個Cookie,但是這個Cookie內部使用&符號,設置了多部分的內容。因此,讀取這個Cookie的時候,就要自行解析,得到多個鍵值對。這樣就規避了cookie的數量限制。
**2、localStorage和sessionStorage**
`localStorage`和`sessionStorage`這兩個屬性都代表同一個Storage對象(一個持久化關聯數組,數組使用字符串來索引,存儲的值都是字符串形式的)。
**2.1 存儲有效期和作用域**
`localStorage`和`sessionStorage`的區別在于存儲的有效期和作用域的不同。
**2.1.1 localStorage**
通過`localStorage`存儲的數據是永久性的,除非Web應用刻意刪除存儲的數據或用戶通過設置瀏覽器設置來刪除,否則數據將一直保留在用戶的電腦里,永不過期。
`localStorage`的作用域是限定在文檔源(document origin)級別。
同源的文檔間共享同樣的`localStorage`數據。
**2.1.2 sessionStorage**
`sessionStorage`的作用域同樣是限定在文檔源中,不過它被限定在窗口中。也就是說,如果同源的文檔在不同的瀏覽器標簽頁中,那它們互相之間擁有的是各自的`sessionStorage`數據,無法共享。
注意:基于窗口作用域的`sessionStorage`指的窗口只是頂級窗口。如果一個瀏覽器標簽頁包含多個`<iframe>`元素,它們包含的文檔是同源的,兩者之間的sessionStorage是可共享的。
**2.2 存儲API**
我們可以將`localStorage`和`sessionStorage`當做普通的JavaScript對象:通過設置屬性來存儲字符串值,查詢該屬性來讀取該值。
```
localStorage.user = 'TG';
```
當然,這兩個對象也提供了對應的存儲(`setItem()`)和讀取(`getItem()`)的方法。
```
localStorage.setItem('user','TG'); //存儲一個以“user”的名字存儲的數值。
localStorage.getItem('user'); //讀取
```
同樣,`sessionStorage`也有這兩個方法。
```
sessionStorage.setItem('user','TG');
sessiontStorage.getItem('user');
```
還可以使用`removeItem()`和`clear()`方法來刪除。
```
localStorage.removeItem('user'); //刪除名為“user”的數據。
localStorage.clear(); //刪除所有存儲的數據
sessionStorage.removeItem('user');
sessionStorage.clear();
```
遍歷存儲數據。
```
for(var i=0; i < localStorage.length; i++){
var name = localStorage.key(i); //獲取第i對的名字
console.log(localStorage.getItem(name); //獲取該對的值
}
```
其中的`key`方法,根據位置(從0開始)獲得鍵值。
```
localStorage.key(1);
```
**2.3 storage事件**
當儲存的數據發生變化時,會觸發storage事件。我們可以指定這個事件的回調函數。
```
window.addEventListener("storage",onStorageChange);
```
回調函數接受一個event對象作為參數。這個event對象的key屬性,保存發生變化的鍵名。
```
function onStorageChange(e) {
console.log(e.key);
}
```
除了key屬性,event對象的屬性還有三個:
- oldValue:更新前的值。如果該鍵為新增加,則這個屬性為null。
- newValue:更新后的值。如果該鍵被刪除,則這個屬性為null。
- url:原始觸發storage事件的那個網頁的網址。
值得特別注意的是,該事件不在導致數據變化的當前頁面觸發。如果瀏覽器同時打開一個域名下面的多個頁面,當其中的一個頁面改變sessionStorage或localStorage的數據時,其他所有頁面的storage事件會被觸發,而原始頁面并不觸發storage事件。可以通過這種機制,實現多個窗口之間的通信。所有瀏覽器之中,只有IE瀏覽器除外,它會在所有頁面觸發storage事件。
**3、客戶端數據庫(IndexedDB)**
IndexedDB(對象數據庫)可以說是瀏覽器端數據庫,可以被網頁腳本程序創建和操作。它允許儲存大量數據,提供查找接口,還能建立索引。
在IndexedDB API中,一個數據庫就是一個命名對象倉庫(object store)的集合,對象存儲區存儲的是對象。
IndexedDB特點:
- 鍵值對存儲:在對象倉庫中,數據以“鍵值對”的形式保存,每一個數據都有對應的鍵名,鍵名是獨一無二的,不能有重復,否則會拋出一個錯誤。
- 同域限制:IndexedDB數據庫的作用域是限制在包含它們的文檔源中,每一個數據庫對應創建該數據庫的域名,兩個同源的Web頁面互相之間可以訪問對方的數據,但非同源的頁面就不行。
- 支持事務:IndexedDB支持事務,這就是說對數據庫的查詢和更新都是包含在一個事務(transaction)中,以此來確保這些操作要么是一起成功,要么是一起失敗,并且永遠不會讓數據庫出現更新到一半的情況。
- 異步:IndexedDB的操作不會阻塞瀏覽器的UI主線程。
- 儲存空間大:IE的儲存上限是250MB,Chrome和Opera是剩余空間的某個百分比,Firefox則沒有上限。
**3.1 檢測瀏覽器是否支持IndexedDB API**
```
if('indexedDB' in window){
//支持
}else{
//不支持
}
```
**3.2 訪問數據庫**
要異步訪問數據庫,就要調用 window 對象 indexedDB 屬性的 open() 方法
```
var request = indexedDB.open(name[,version])
```
indexedDB.open方法可傳輸人兩個參數:name是數據庫名稱,必填;version是數據庫版本,是一個大于0的正整數(0將報錯)。
open方法返回一個 IDBRequest 對象 (IDBOpenDBRequest),
注意:如果數據庫存在,將打開數據庫,否則,則會新建該數據庫。如果省略第二個參數,則會自動創建版本為1的該數據庫。
當打開數據時,有可能觸發4種事件:
success:打開成功。
error:打開失敗。
upgradeneeded:第一次打開該數據庫,或者數據庫版本發生變化。
blocked:上一次的數據庫連接還未關閉。
第一次打開數據庫時,會先觸發upgradeneeded事件,然后觸發success事件。
```
request.onupgradeneeded = function(e){}
request.onsuccess = function(e){
db = e.target.result;
}
```
回調函數接受一個事件對象event作為參數,它的target.result屬性就指向打開的IndexedDB數據庫。
**3.3 IndexedDB實例對象的方法**
**3.3.1 createObjectStore()方法**
createObjectStore()方法用于創建存放數據的“對象倉庫”(object store)。
```
db.createObjectStore(name[,options]);
```
參數說明:
參數name是對象倉庫的名字;options是可選參數,用來設置對象倉庫的屬性,可配置兩個屬性:keyPath和autoIncrement,分別表示每條記錄的鍵名和是否使用自動遞增的整數作為鍵名,默認為false。
```
db.createObjectStore('db1', {keyPath: 'user'});
db.createObjectStore('db2', {autoIncrement: true});
```
由于對象倉庫的名字具有唯一性(當創建已存在的數據庫時,會報錯),所以在創建對象倉庫時,我們有必要檢測對象倉庫是否已存在:
db.objectStoreNames.contains(name)
objectStoreNames屬性返回一個DOMStringList對象,里面包含了當前數據庫所有“對象倉庫”的名稱。可以使用DOMStringList對象的contains方法,檢查數據庫是否包含某個“對象倉庫”。
**3.3.2 transaction方法**
創建了數據庫,當然要使用它,不過數據庫的更新、讀取和刪除是建立在事務的基礎上的,所以我們首先要創建一個事務:
```
var t = db.transaction(array,type)
```
transcation()方法接受兩個參數:參數array是一個數組,包含了所要使用的對象倉庫,通常是一個;參數type是一個表示操作類型的字符串,目前只有兩種類型:readonly(只讀)和readwrite(讀寫)。
```
t = db.transaction(['db1','readwrite');
```
transaction()方法返回一個事務對象,該對象的objectStore()方法用于獲取指定的對象倉庫:
```
var store = t.objectStore('db1');
```
事務對象有三個監聽事件:
abort:事務中斷。
complete:事務完成。
error:事務出錯。
假如事務完成時:
```
t.oncomplete =function(e){}
```
**3.3.3 數據操作**
下面的方法都是在事件對象上。
**(1)add()方法**
add()方法用來添加數據
```
var add = store.add(data,key)
```
參數說明:參數data是所要添加的數據;參數key是這條數據對應的鍵名(key)。
add()方法是異步的,有success和error事件:
```
add.onsuccess = funciton(e){}
add.onerror = function(e){}
```
**(2)get()方法**
get()方法用來讀取數據,它的參數是鍵名
```
store.get(key)
```
get方法也是異步的,也有success和error事件。
**(3)put()方法**
put()方法用來更新數據,與add()方法類似:
```
var update = store.put(data,key)
```
**(4)delete()方法**
delete()方法用來刪除數據,它的參數是鍵名:
```
var delete = store.delete(key)
```
delete方法也是異步的,也有success和error事件。
**(5)openCursor()方法**
openCursor()方法用來遍歷數據:
```
var cursor = store.openCursor()
```
openCursor方法也是異步的,也有success和error事件。
```
cursor.onsuccess = function(e){
var res = e.target.result;
console.log('key',res.key);
console.log('data',res.value);
res.continue()
}
```
e.target.result屬性指向當前數據對象。當前數據對象的key和value分別返回鍵名和鍵值(即實際存入的數據)。continue方法將光標移到下一個數據對象,如果當前數據對象已經是最后一個數據了,則光標指向null。
openCursor方法還可以接受第二個參數,表示遍歷方向,默認值為next,其他可能的值為prev、nextunique和prevunique。后兩個值表示如果遇到重復值,會自動跳過。
**3.3.4 createIndex()方法**
createIndex()方法用來創建索引:
```
createIndex(index,name,options)
```
createIndex方法接受三個參數,第一個是索引名稱,第二個是建立索引的屬性名,第三個是參數對象,用來設置索引特性。unique表示索引所在的屬性是否有唯一值.
**3.3.5 index方法**
index()方法用于從對象倉庫返回指定的索引。
```
var index = store.index(index);
var data = index.get(name)
```
注意:get方法有可能取回多個數據對象,因為name屬性沒有唯一值。
**4、同源策略**
瀏覽器的同源政策規定,兩個網址只要域名相同和端口相同,就可以共享Cookie。
注意:這里不要求協議相同。也就是說,`http://example.com設置的Cookie,可以被https://example.com讀取`。
- 前言
- JavaScript簡介
- 基本概念
- 語法
- 數據類型
- 運算符
- 表達式
- 語句
- 對象
- 數組
- 函數
- 引用類型(對象)
- Object對象
- Array對象
- Date對象
- RegExp對象
- 基本包裝類型(Boolean、Number、String)
- 單體內置對象(Global、Math)
- console對象
- DOM
- DOM-屬性和CSS
- BOM
- Event 事件
- 正則表達式
- JSON
- AJAX
- 表單和富文本編輯器
- 表單
- 富文本編輯器
- canvas
- 離線應用
- 客戶端存儲(Cookie、Storage、IndexedDB)
- HTML5 API
- Video/Audio
- Geolocation API
- requestAnimationFrame
- File API
- FullScreen API
- IndexedDB
- 檢測設備方向
- Blob
- vibrate
- Luminosity API
- WebRTC
- Page Visibility API
- Performance API
- Web Speech
- Notification
- 面向對象的程序設計
- 概述
- this關鍵字
- 原型鏈
- 作用域
- 常用API合集
- SVG
- 錯誤處理機制
- JavaScript開發技巧合集
- 編程風格
- 垃圾回收機制