## 离线应用与客户端存储
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开发技巧合集
- 编程风格
- 垃圾回收机制